blocklistd-helper revision 1.10 1 1.1 christos #!/bin/sh
2 1.1 christos #echo "run $@" 1>&2
3 1.1 christos #set -x
4 1.1 christos # $1 command
5 1.1 christos # $2 rulename
6 1.1 christos # $3 protocol
7 1.1 christos # $4 address
8 1.1 christos # $5 mask
9 1.1 christos # $6 port
10 1.1 christos # $7 id
11 1.1 christos
12 1.1 christos pf=
13 1.1 christos if [ -f "/etc/ipfw-blocklist.rc" ]; then
14 1.1 christos pf="ipfw"
15 1.1 christos . /etc/ipfw-blocklist.rc
16 1.1 christos ipfw_offset=${ipfw_offset:-2000}
17 1.7 christos fi
18 1.7 christos
19 1.7 christos if [ -z "$pf" ]; then
20 1.10 christos for f in npf pf ipfilter ipfw; do
21 1.10 christos if [ -x /etc/rc.d/$f ]; then
22 1.10 christos if /etc/rc.d/$f status >/dev/null 2>&1; then
23 1.10 christos pf="$f"
24 1.10 christos break
25 1.10 christos fi
26 1.10 christos elif [ -f "/etc/$f.conf" ]; then
27 1.10 christos # xxx assume a config file means it can be enabled --
28 1.10 christos # and the first one wins!
29 1.1 christos pf="$f"
30 1.1 christos break
31 1.1 christos fi
32 1.1 christos done
33 1.1 christos fi
34 1.1 christos
35 1.2 christos if [ -z "$pf" -a -x "/sbin/iptables" ]; then
36 1.2 christos pf="iptables"
37 1.2 christos fi
38 1.2 christos
39 1.1 christos if [ -z "$pf" ]; then
40 1.1 christos echo "$0: Unsupported packet filter" 1>&2
41 1.1 christos exit 1
42 1.1 christos fi
43 1.1 christos
44 1.2 christos flags=
45 1.1 christos if [ -n "$3" ]; then
46 1.2 christos raw_proto="$3"
47 1.1 christos proto="proto $3"
48 1.2 christos if [ $3 = "tcp" ]; then
49 1.2 christos flags="flags S/SAFR"
50 1.2 christos fi
51 1.1 christos fi
52 1.1 christos
53 1.1 christos if [ -n "$6" ]; then
54 1.2 christos raw_port="$6"
55 1.1 christos port="port $6"
56 1.1 christos fi
57 1.1 christos
58 1.1 christos addr="$4"
59 1.1 christos mask="$5"
60 1.1 christos case "$4" in
61 1.1 christos ::ffff:*.*.*.*)
62 1.1 christos if [ "$5" = 128 ]; then
63 1.1 christos mask=32
64 1.1 christos addr=${4#::ffff:}
65 1.1 christos fi;;
66 1.1 christos esac
67 1.1 christos
68 1.1 christos case "$1" in
69 1.1 christos add)
70 1.1 christos case "$pf" in
71 1.10 christos ipfilter)
72 1.2 christos # N.B.: If you reload /etc/ipf.conf then you need to stop and
73 1.10 christos # restart blocklistd (and make sure blocklistd_flags="-r").
74 1.10 christos # This should normally already be implemented in
75 1.10 christos # /etc/rc.d/ipfilter, but if then not add the following lines to
76 1.10 christos # the end of the ipfilter_reload() function:
77 1.10 christos #
78 1.10 christos # if checkyesnox blocklistd; then
79 1.10 christos # /etc/rc.d/blocklistd restart
80 1.10 christos # fi
81 1.2 christos #
82 1.2 christos # XXX we assume the following rule is present in /etc/ipf.conf:
83 1.10 christos # (should we check? -- it probably cannot be added dynamically)
84 1.2 christos #
85 1.2 christos # block in proto tcp/udp from any to any head blocklistd
86 1.2 christos #
87 1.2 christos # where "blocklistd" is the default rulename (i.e. "$2")
88 1.2 christos #
89 1.2 christos # This rule can come before any rule that logs connections,
90 1.2 christos # etc., and should be followed by final rules such as:
91 1.2 christos #
92 1.2 christos # # log all as-yet unblocked incoming TCP connection
93 1.2 christos # # attempts
94 1.2 christos # log in proto tcp from any to any flags S/SAFR
95 1.2 christos # # last "pass" match wins for all non-blocked packets
96 1.2 christos # pass in all
97 1.2 christos # pass out all
98 1.2 christos #
99 1.2 christos # I.e. a "pass" rule which will be the final match and override
100 1.2 christos # the "block". This way the rules added by blocklistd will
101 1.2 christos # actually block packets, and prevent logging of them as
102 1.2 christos # connections, because they include the "quick" flag.
103 1.2 christos #
104 1.10 christos # N.b.: $port is not included/used in rules -- abusers are cut
105 1.10 christos # off completely from all services!
106 1.2 christos #
107 1.2 christos # Note RST packets are not returned for blocked SYN packets of
108 1.2 christos # active attacks, so the port will not appear to be closed.
109 1.2 christos # This will probably give away the fact that a firewall has been
110 1.2 christos # triggered to block connections, but it prevents generating
111 1.2 christos # extra outbound traffic, and it may also slow down the attacker
112 1.2 christos # somewhat.
113 1.2 christos #
114 1.2 christos # Note also that we don't block all packets, just new attempts
115 1.2 christos # to open connections (see $flags above). This allows us to do
116 1.2 christos # counterespionage against the attacker (or continue to make use
117 1.2 christos # of any other services that might be on the same subnet as the
118 1.10 christos # supposed attacker). However it does not kill any active
119 1.10 christos # connections -- we rely on the reporting daemon to do its own
120 1.10 christos # protection and cleanup.
121 1.2 christos #
122 1.10 christos # N.B.: The rule generated here must exactly match the
123 1.10 christos # corresponding rule generated for the "rem" command below!
124 1.2 christos #
125 1.2 christos echo block in log quick $proto \
126 1.9 christos from $addr/$mask to any $flags group $2 | \
127 1.2 christos /sbin/ipf -A -f - >/dev/null 2>&1 && echo OK
128 1.1 christos ;;
129 1.2 christos
130 1.1 christos ipfw)
131 1.1 christos # use $ipfw_offset+$port for rule number
132 1.1 christos rule=$(($ipfw_offset + $6))
133 1.1 christos tname="port$6"
134 1.1 christos /sbin/ipfw table $tname create type addr 2>/dev/null
135 1.1 christos /sbin/ipfw -q table $tname add "$addr/$mask"
136 1.1 christos # if rule number $rule does not already exist, create it
137 1.1 christos /sbin/ipfw show $rule >/dev/null 2>&1 || \
138 1.1 christos /sbin/ipfw add $rule drop $3 from \
139 1.1 christos table"("$tname")" to any dst-port $6 >/dev/null && \
140 1.1 christos echo OK
141 1.1 christos ;;
142 1.2 christos
143 1.2 christos iptables)
144 1.2 christos if ! /sbin/iptables --list "$2" >/dev/null 2>&1; then
145 1.2 christos /sbin/iptables --new-chain "$2"
146 1.2 christos fi
147 1.2 christos /sbin/iptables --append INPUT --proto "$raw_proto" \
148 1.2 christos --dport "$raw_port" --jump "$2"
149 1.2 christos /sbin/iptables --append "$2" --proto "$raw_proto" \
150 1.2 christos --source "$addr/$mask" --dport "$raw_port" --jump DROP
151 1.2 christos echo OK
152 1.2 christos ;;
153 1.2 christos
154 1.1 christos npf)
155 1.1 christos /sbin/npfctl rule "$2" add block in final $proto from \
156 1.1 christos "$addr/$mask" to any $port
157 1.1 christos ;;
158 1.2 christos
159 1.1 christos pf)
160 1.1 christos # if the filtering rule does not exist, create it
161 1.1 christos /sbin/pfctl -a "$2/$6" -sr 2>/dev/null | \
162 1.1 christos grep -q "<port$6>" || \
163 1.1 christos echo "block in quick $proto from <port$6> to any $port" | \
164 1.1 christos /sbin/pfctl -a "$2/$6" -f -
165 1.1 christos # insert $ip/$mask into per-protocol/port anchored table
166 1.4 christos /sbin/pfctl -qa "$2/$6" -t "port$6" -T add "$addr/$mask" && \
167 1.5 christos /sbin/pfctl -qk "$addr" && echo OK
168 1.1 christos ;;
169 1.2 christos
170 1.1 christos esac
171 1.1 christos ;;
172 1.1 christos rem)
173 1.1 christos case "$pf" in
174 1.10 christos ipfilter)
175 1.10 christos # N.B.: The rule generated here must exactly match the
176 1.10 christos # corresponding rule generated for the "add" command above!
177 1.10 christos #
178 1.2 christos echo block in log quick $proto \
179 1.9 christos from $addr/$mask to any $flags group $2 | \
180 1.2 christos /sbin/ipf -A -r -f - >/dev/null 2>&1 && echo OK
181 1.1 christos ;;
182 1.2 christos
183 1.1 christos ipfw)
184 1.1 christos /sbin/ipfw table "port$6" delete "$addr/$mask" 2>/dev/null && \
185 1.1 christos echo OK
186 1.1 christos ;;
187 1.2 christos
188 1.2 christos iptables)
189 1.2 christos if /sbin/iptables --list "$2" >/dev/null 2>&1; then
190 1.2 christos /sbin/iptables --delete "$2" --proto "$raw_proto" \
191 1.2 christos --source "$addr/$mask" --dport "$raw_port" \
192 1.2 christos --jump DROP
193 1.2 christos fi
194 1.2 christos echo OK
195 1.2 christos ;;
196 1.2 christos
197 1.1 christos npf)
198 1.1 christos /sbin/npfctl rule "$2" rem-id "$7"
199 1.1 christos ;;
200 1.2 christos
201 1.1 christos pf)
202 1.4 christos /sbin/pfctl -qa "$2/$6" -t "port$6" -T delete "$addr/$mask" && \
203 1.1 christos echo OK
204 1.1 christos ;;
205 1.2 christos
206 1.1 christos esac
207 1.1 christos ;;
208 1.1 christos flush)
209 1.1 christos case "$pf" in
210 1.10 christos ipfilter)
211 1.2 christos #
212 1.10 christos # N.B. WARNING: This is obviously not reentrant!
213 1.2 christos #
214 1.10 christos # First we flush all the rules from the inactive set, then we
215 1.10 christos # reload the ones that do not belong to the group "$2", and
216 1.10 christos # finally we swap the active and inactive rule sets.
217 1.2 christos #
218 1.2 christos /sbin/ipf -I -F a
219 1.10 christos #
220 1.10 christos # "ipf -I -F a" also flushes active accounting rules!
221 1.10 christos #
222 1.10 christos # Note that accounting rule groups are unique to accounting
223 1.10 christos # rules and have nothing to do with filter rules, though of
224 1.10 christos # course theoretically one could use the same group name for
225 1.10 christos # them too.
226 1.10 christos #
227 1.10 christos # In theory anyone using any such accounting rules should have a
228 1.10 christos # wrapper /etc/rc.conf.d/blocklistd script (and corresponding
229 1.10 christos # /etc/rc.conf.d/ipfilter script) that will record and
230 1.10 christos # consolidate the values accumulated by such accounting rules
231 1.10 christos # before they are flushed, since otherwise their counts will be
232 1.10 christos # lost forever.
233 1.10 christos #
234 1.2 christos /usr/sbin/ipfstat -io | fgrep -v "group $2" | \
235 1.2 christos /sbin/ipf -I -f - >/dev/null 2>&1
236 1.10 christos #
237 1.10 christos # This MUST be done last and separately as "-s" is executed
238 1.2 christos # _while_ the command arguments are being processed!
239 1.10 christos #
240 1.2 christos /sbin/ipf -s && echo OK
241 1.1 christos ;;
242 1.2 christos
243 1.1 christos ipfw)
244 1.1 christos /sbin/ipfw table "port$6" flush 2>/dev/null && echo OK
245 1.1 christos ;;
246 1.2 christos
247 1.2 christos iptables)
248 1.2 christos if /sbin/iptables --list "$2" >/dev/null 2>&1; then
249 1.2 christos /sbin/iptables --flush "$2"
250 1.2 christos fi
251 1.2 christos echo OK
252 1.2 christos ;;
253 1.2 christos
254 1.1 christos npf)
255 1.1 christos /sbin/npfctl rule "$2" flush
256 1.1 christos ;;
257 1.2 christos
258 1.1 christos pf)
259 1.4 christos # dynamically determine which anchors exist
260 1.8 christos for anchor in $(/sbin/pfctl -a "$2" -s Anchors 2> /dev/null); do
261 1.9 christos /sbin/pfctl -a "$anchor" -t "port${anchor##*/}" -T flush
262 1.9 christos /sbin/pfctl -a "$anchor" -F rules
263 1.4 christos done
264 1.4 christos echo OK
265 1.1 christos ;;
266 1.1 christos esac
267 1.1 christos ;;
268 1.1 christos *)
269 1.1 christos echo "$0: Unknown command '$1'" 1>&2
270 1.1 christos exit 1
271 1.1 christos ;;
272 1.1 christos esac
273