]>
Commit | Line | Data |
---|---|---|
1 | #!/bin/bash | |
2 | ############################################################################### | |
3 | # # | |
4 | # IPFire.org - A linux based firewall # | |
5 | # Copyright (C) 2012-2013 IPFire Network Development Team # | |
6 | # # | |
7 | # This program is free software: you can redistribute it and/or modify # | |
8 | # it under the terms of the GNU General Public License as published by # | |
9 | # the Free Software Foundation, either version 3 of the License, or # | |
10 | # (at your option) any later version. # | |
11 | # # | |
12 | # This program is distributed in the hope that it will be useful, # | |
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
15 | # GNU General Public License for more details. # | |
16 | # # | |
17 | # You should have received a copy of the GNU General Public License # | |
18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
19 | # # | |
20 | ############################################################################### | |
21 | ||
22 | IPTABLES_TABLES="filter mangle nat" | |
23 | ||
24 | function iptables() { | |
25 | local protocol="${1}" | |
26 | assert isset protocol | |
27 | shift | |
28 | ||
29 | # Rules go to the filter table by default | |
30 | local table="filter" | |
31 | ||
32 | # Argument list | |
33 | local args | |
34 | ||
35 | # Cached arguments | |
36 | local src dst | |
37 | ||
38 | # Parsing arguments | |
39 | while [ $# -gt 0 ]; do | |
40 | case "${1}" in | |
41 | # Filter to which table this rule should go. | |
42 | -t) | |
43 | table="${2}" | |
44 | shift 2 | |
45 | ||
46 | assert isoneof table ${IPTABLES_TABLES} | |
47 | ;; | |
48 | ||
49 | # Automatically convert ICMP to ICMPv6 for IPv6 | |
50 | --protocol|-p) | |
51 | local proto="${2}" | |
52 | ||
53 | if [ "${protocol}" = "ipv6" -a "${proto}" = "icmp" ]; then | |
54 | proto="icmpv6" | |
55 | fi | |
56 | ||
57 | list_append args "${1} ${proto}" | |
58 | shift 2 | |
59 | ;; | |
60 | *) | |
61 | list_append args "${1}" | |
62 | ||
63 | # Save some values for further processing. | |
64 | case "${1}" in | |
65 | -s) | |
66 | src="${2}" | |
67 | ;; | |
68 | -d) | |
69 | dst="${2}" | |
70 | ;; | |
71 | esac | |
72 | shift | |
73 | ;; | |
74 | esac | |
75 | done | |
76 | ||
77 | assert isset action | |
78 | ||
79 | # Check if given IP addresses or networks match the protocol version. | |
80 | local src_proto | |
81 | if isset src; then | |
82 | src_proto="$(ip_detect_protocol ${src})" | |
83 | ||
84 | assert [ "${protocol}" = "${src_proto}" ] | |
85 | fi | |
86 | ||
87 | local dst_proto | |
88 | if isset dst; then | |
89 | dst_proto="$(ip_detect_protocol ${dst})" | |
90 | ||
91 | assert [ "${protocol}" = "${dst_proto}" ] | |
92 | fi | |
93 | ||
94 | # Check if the directory where we put our rules in is set and | |
95 | # exists. | |
96 | assert isset IPTABLES_TMPDIR | |
97 | local rulesfile="${IPTABLES_TMPDIR}/${protocol}-${table}" | |
98 | ||
99 | print "${args}" >> "${rulesfile}" | |
100 | assert_check_retval $? | |
101 | } | |
102 | ||
103 | function iptables_chain_create() { | |
104 | local protocol="${1}" | |
105 | assert isset protocol | |
106 | shift | |
107 | ||
108 | local chain | |
109 | local table="filter" | |
110 | local policy="-" | |
111 | ||
112 | while [ $# -gt 0 ]; do | |
113 | case "${1}" in | |
114 | -t) | |
115 | table="${2}" | |
116 | shift | |
117 | ;; | |
118 | --policy=*) | |
119 | policy="$(cli_get_val ${1})" | |
120 | ;; | |
121 | -*) | |
122 | log WARNING "Unrecognized argument: ${1}" | |
123 | ;; | |
124 | *) | |
125 | chain=${1} | |
126 | ;; | |
127 | esac | |
128 | shift | |
129 | done | |
130 | ||
131 | assert isset chain | |
132 | assert isset table | |
133 | assert isoneof policy ACCEPT DROP "-" | |
134 | ||
135 | iptables "${protocol}" -t "${table}" ":${chain} ${policy} [0:0]" | |
136 | } | |
137 | ||
138 | # Calls the binary iptables command. | |
139 | function _iptables() { | |
140 | local protocol="${1}" | |
141 | assert isset protocol | |
142 | shift | |
143 | ||
144 | local cmd | |
145 | case "${protocol}" in | |
146 | ipv6) | |
147 | cmd="ip6tables" | |
148 | ;; | |
149 | ipv4) | |
150 | cmd="iptables" | |
151 | ;; | |
152 | esac | |
153 | assert isset cmd | |
154 | cmd="$(which ${cmd})" | |
155 | ||
156 | cmd "${cmd}" "$@" | |
157 | return $? | |
158 | } | |
159 | ||
160 | function iptables_status() { | |
161 | local protocol="${1}" | |
162 | assert isset protocol | |
163 | ||
164 | local table | |
165 | for table in ${IPTABLES_TABLES}; do | |
166 | print "${protocol} - ${table}:" | |
167 | _iptables "${protocol}" -t "${table}" -L -n -v | |
168 | ||
169 | done | |
170 | ||
171 | return ${EXIT_OK} | |
172 | } | |
173 | ||
174 | function iptables_rulesfile() { | |
175 | local proto=${1} | |
176 | proto=${proto/ipv/} | |
177 | ||
178 | local chain=${2} | |
179 | [ -z "${chain}" ] && chain="ruleset" | |
180 | ||
181 | print "${IPTABLES_TMPDIR}/${chain}${proto}" | |
182 | } | |
183 | ||
184 | function iptables_init() { | |
185 | local protocol="${1}" | |
186 | assert isset protocol | |
187 | ||
188 | local policy="${2}" | |
189 | assert isset policy | |
190 | ||
191 | # Create filter table and initialize chains. | |
192 | iptables "${protocol}" "* filter" | |
193 | iptables_chain_create "${protocol}" -t filter INPUT --policy="${policy}" | |
194 | iptables_chain_create "${protocol}" -t filter OUTPUT --policy="${policy}" | |
195 | iptables_chain_create "${protocol}" -t filter FORWARD --policy="${policy}" | |
196 | ||
197 | # Create mangle table and initialize chains. | |
198 | iptables "${protocol}" -t mangle "* mangle" | |
199 | iptables_chain_create "${protocol}" -t mangle PREROUTING --policy="ACCEPT" | |
200 | iptables_chain_create "${protocol}" -t mangle INPUT --policy="ACCEPT" | |
201 | iptables_chain_create "${protocol}" -t mangle OUTPUT --policy="ACCEPT" | |
202 | iptables_chain_create "${protocol}" -t mangle FORWARD --policy="ACCEPT" | |
203 | iptables_chain_create "${protocol}" -t mangle POSTROUTING --policy="ACCEPT" | |
204 | ||
205 | # Create NAT table and initialize chains. | |
206 | iptables "${protocol}" -t nat "* nat" | |
207 | iptables_chain_create "${protocol}" -t nat PREROUTING --policy="ACCEPT" | |
208 | iptables_chain_create "${protocol}" -t nat OUTPUT --policy="ACCEPT" | |
209 | iptables_chain_create "${protocol}" -t nat POSTROUTING --policy="ACCEPT" | |
210 | } | |
211 | ||
212 | # Load the created ruleset into the kernel. | |
213 | function iptables_commit () { | |
214 | local protocol="${1}" | |
215 | assert isset protocol | |
216 | shift | |
217 | ||
218 | local testmode="false" | |
219 | ||
220 | while [ $# -gt 0 ]; do | |
221 | case "${1}" in | |
222 | --test) | |
223 | testmode="true" | |
224 | ;; | |
225 | *) | |
226 | log WARNING "Unrecognized argument: ${1}" | |
227 | ;; | |
228 | esac | |
229 | shift | |
230 | done | |
231 | ||
232 | # Concat all rules into one big file. | |
233 | local rulesfile="${IPTABLES_TMPDIR}/ruleset" | |
234 | _iptables_commit_cat_rulesfile "${protocol}" "${rulesfile}" | |
235 | ||
236 | # Run the following loop twice: | |
237 | # 1st: Check if the ruleset can be loaded | |
238 | # 2nd: If not in test mode, actually load the ruleset into the kernel | |
239 | local load_cmd="--test" | |
240 | local ret=0 | |
241 | ||
242 | local i | |
243 | for i in 0 1; do | |
244 | _iptables_commit_load_rulesfile "${protocol}" "${rulesfile}" "${load_cmd}" | |
245 | ret=$? | |
246 | ||
247 | case "${i},${ret}" in | |
248 | 0,${EXIT_OK}) | |
249 | iptables_dump "${protocol}" "${rulesfile}" --log-facility="DEBUG" | |
250 | log DEBUG "Ruleset load check succeeded (${protocol})" | |
251 | ;; | |
252 | ||
253 | # Loading rules has failed (test) | |
254 | 0,*) | |
255 | iptables_dump "${protocol}" "${rulesfile}" --log-facility="CRITICAL" | |
256 | log CRITICAL "Ruleset load check failed (${protocol} - ${ret})" | |
257 | return ${ret} | |
258 | ;; | |
259 | ||
260 | 1,${EXIT_OK}) | |
261 | log DEBUG "Ruleset successfully loaded (${protocol})" | |
262 | return ${EXIT_OK} | |
263 | ;; | |
264 | ||
265 | 1,*) | |
266 | log CRITICAL "Ruleset loading failed (${protocol})" | |
267 | return ${ret} | |
268 | ;; | |
269 | esac | |
270 | ||
271 | # Skip the second loop iteration, if we are running in test mode. | |
272 | enabled testmode && break | |
273 | ||
274 | load_cmd="" | |
275 | done | |
276 | ||
277 | return ${EXIT_OK} | |
278 | } | |
279 | ||
280 | function _iptables_commit_cat_rulesfile() { | |
281 | local protocol="${1}" | |
282 | assert isset protocol | |
283 | ||
284 | local rulesfile="${2}" | |
285 | assert isset rulesfile | |
286 | ||
287 | local table | |
288 | local file | |
289 | for table in ${IPTABLES_TABLES}; do | |
290 | file="${IPTABLES_TMPDIR}/${protocol}-${table}" | |
291 | ||
292 | fread "${file}" | |
293 | ||
294 | # Add the COMMIT statement for every table. | |
295 | print "COMMIT" | |
296 | done > "${rulesfile}" | |
297 | ||
298 | assert [ -s "${rulesfile}" ] | |
299 | } | |
300 | ||
301 | function _iptables_commit_load_rulesfile() { | |
302 | local protocol="${1}" | |
303 | assert isset protocol | |
304 | ||
305 | local rulesfile="${2}" | |
306 | assert isset rulesfile | |
307 | shift 2 | |
308 | ||
309 | local testmode="false" | |
310 | while [ $# -gt 0 ]; do | |
311 | case "${1}" in | |
312 | --test) | |
313 | testmode="true" | |
314 | ;; | |
315 | esac | |
316 | shift | |
317 | done | |
318 | ||
319 | local iptables_cmd | |
320 | case "${protocol}" in | |
321 | ipv6) | |
322 | iptables_cmd="ip6tables-restore" | |
323 | ;; | |
324 | ipv4) | |
325 | iptables_cmd="iptables-restore" | |
326 | ;; | |
327 | esac | |
328 | assert isset iptables_cmd | |
329 | ||
330 | if enabled testmode; then | |
331 | list_append iptables_cmd "--test" | |
332 | fi | |
333 | ||
334 | # Save when importing the rules has started. | |
335 | local time_started="$(timestamp)" | |
336 | ||
337 | cmd "${iptables_cmd}" < "${rulesfile}" | |
338 | local ret=$? | |
339 | ||
340 | case "${ret}" in | |
341 | ${EXIT_OK}) | |
342 | local time_finished="$(timestamp)" | |
343 | time_finished="$(( ${time_finished} - ${time_started} ))" | |
344 | ||
345 | enabled testmode && return ${EXIT_OK} | |
346 | ||
347 | log INFO "Successfully loaded new firewall ruleset for ${protocol} in ${time_finished}s!" | |
348 | ;; | |
349 | *) | |
350 | if ! enabled testmode; then | |
351 | log CRITICAL "Error loading firewall ruleset for ${protocol}!" | |
352 | fi | |
353 | ;; | |
354 | esac | |
355 | ||
356 | return ${ret} | |
357 | } | |
358 | ||
359 | function iptables_dump() { | |
360 | local protocol="${1}" | |
361 | assert isset protocol | |
362 | ||
363 | local rulesfile="${2}" | |
364 | assert isset rulesfile | |
365 | shift 2 | |
366 | ||
367 | local log_facility="INFO" | |
368 | ||
369 | while [ $# -gt 0 ]; do | |
370 | case "${1}" in | |
371 | --log-facility=*) | |
372 | log_facility="$(cli_get_val ${1})" | |
373 | ;; | |
374 | *) | |
375 | log WARNING "Unrecognized argument: ${1}" | |
376 | ;; | |
377 | esac | |
378 | shift | |
379 | done | |
380 | ||
381 | # Say what we are going to do: | |
382 | log "${log_facility}" "Firewall ruleset for ${protocol}:" | |
383 | ||
384 | local counter="0" | |
385 | local line | |
386 | while read -r line; do | |
387 | counter="$(( ${counter} + 1 ))" | |
388 | ||
389 | printf -v line "%4d | %s" "${counter}" "${line}" | |
390 | log "${log_facility}" "${line}" | |
391 | done < "${rulesfile}" | |
392 | } | |
393 | ||
394 | function iptables_LOG() { | |
395 | local prefix="${1}" | |
396 | local ret | |
397 | ||
398 | # Automatically append a colon and whitespace. | |
399 | case "${prefix}" in | |
400 | # Everything is fine. | |
401 | "*: ") ;; | |
402 | ||
403 | # Ends with colon, add whitespace only. | |
404 | "*:") | |
405 | prefix="${prefix} " | |
406 | ;; | |
407 | ||
408 | # Append both. | |
409 | *) | |
410 | prefix="${prefix}: " | |
411 | ;; | |
412 | esac | |
413 | ||
414 | case "${FIREWALL_LOG_METHOD}" in | |
415 | nflog) | |
416 | ret="NFLOG --nflog-threshold ${FIREWALL_NFLOG_THRESHOLD}" | |
417 | isset prefix && ret="${ret} --nflog-prefix \"$prefix\"" | |
418 | ;; | |
419 | syslog) | |
420 | ret="LOG" | |
421 | isset prefix && ret="${ret} --log-prefix \"$prefix\"" | |
422 | ;; | |
423 | esac | |
424 | ||
425 | print "${ret}" | |
426 | } | |
427 | ||
428 | function iptables_protocol() { | |
429 | local PROTO | |
430 | PROTO=$1 | |
431 | for proto in tcp udp esp ah; do | |
432 | if [ "$PROTO" = "$proto" ]; then | |
433 | echo "-p $PROTO" | |
434 | break | |
435 | fi | |
436 | done | |
437 | } | |
438 | ||
439 | IPTABLES_PORT=0 | |
440 | IPTABLES_MULTIPORT=1 | |
441 | IPTABLES_PORTRANGE=2 | |
442 | ||
443 | function _iptables_port_range() { | |
444 | grep -q ":" <<< $@ | |
445 | } | |
446 | ||
447 | function _iptables_port_multiport() { | |
448 | grep -q "," <<< $@ | |
449 | } | |
450 | ||
451 | function _iptables_port() { | |
452 | if _iptables_port_range "$@"; then | |
453 | echo $IPTABLES_PORTRANGE | |
454 | elif _iptables_port_multiport "$@"; then | |
455 | echo $IPTABLES_MULTIPORT | |
456 | else | |
457 | echo $IPTABLES_PORT | |
458 | fi | |
459 | } | |
460 | ||
461 | function iptables_source_port() { | |
462 | [ -z "$@" ] && return | |
463 | local type | |
464 | type=$(_iptables_port $@) | |
465 | if [ "$type" = "$IPTABLES_MULTIPORT" ]; then | |
466 | echo "-m multiport --source-ports $@" | |
467 | else | |
468 | echo "--sport $@" | |
469 | fi | |
470 | } | |
471 | ||
472 | function iptables_destination_port() { | |
473 | [ -z "$@" ] && return | |
474 | local type | |
475 | type=$(_iptables_port $@) | |
476 | if [ "$type" = "$IPTABLES_MULTIPORT" ]; then | |
477 | echo "-m multiport --destination-ports $@" | |
478 | else | |
479 | echo "--dport $@" | |
480 | fi | |
481 | } |