]>
Commit | Line | Data |
---|---|---|
98146c00 MT |
1 | #!/bin/bash |
2 | ############################################################################### | |
3 | # # | |
4 | # IPFire.org - A linux based firewall # | |
fe52c5e0 | 5 | # Copyright (C) 2012-2013 IPFire Network Development Team # |
98146c00 MT |
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 | ||
fe52c5e0 MT |
22 | IPTABLES_TABLES="filter mangle nat" |
23 | ||
1c6a4e30 | 24 | iptables() { |
fe52c5e0 MT |
25 | local protocol="${1}" |
26 | assert isset protocol | |
27 | shift | |
28 | ||
29 | # Rules go to the filter table by default | |
afb7d704 | 30 | local table="filter" |
afb7d704 | 31 | |
fe52c5e0 MT |
32 | # Argument list |
33 | local args | |
98146c00 | 34 | |
fe52c5e0 MT |
35 | # Cached arguments |
36 | local src dst | |
98146c00 | 37 | |
98146c00 MT |
38 | # Parsing arguments |
39 | while [ $# -gt 0 ]; do | |
40 | case "${1}" in | |
fe52c5e0 | 41 | # Filter to which table this rule should go. |
98146c00 | 42 | -t) |
fe52c5e0 | 43 | table="${2}" |
98146c00 | 44 | shift 2 |
fe52c5e0 MT |
45 | |
46 | assert isoneof table ${IPTABLES_TABLES} | |
98146c00 | 47 | ;; |
9e65113f MT |
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 | ;; | |
98146c00 | 60 | *) |
fe52c5e0 | 61 | list_append args "${1}" |
afb7d704 MT |
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 | |
98146c00 MT |
72 | shift |
73 | ;; | |
74 | esac | |
75 | done | |
76 | ||
fe52c5e0 | 77 | assert isset action |
afb7d704 | 78 | |
fe52c5e0 | 79 | # Check if given IP addresses or networks match the protocol version. |
afb7d704 | 80 | local src_proto |
fe52c5e0 MT |
81 | if isset src; then |
82 | src_proto="$(ip_detect_protocol ${src})" | |
83 | ||
84 | assert [ "${protocol}" = "${src_proto}" ] | |
85 | fi | |
afb7d704 MT |
86 | |
87 | local dst_proto | |
fe52c5e0 MT |
88 | if isset dst; then |
89 | dst_proto="$(ip_detect_protocol ${dst})" | |
afb7d704 | 90 | |
fe52c5e0 | 91 | assert [ "${protocol}" = "${dst_proto}" ] |
afb7d704 MT |
92 | fi |
93 | ||
fe52c5e0 MT |
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 | ||
1c6a4e30 | 103 | iptables_chain_create() { |
fe52c5e0 MT |
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=*) | |
2212045f | 119 | policy="$(cli_get_val "${1}")" |
afb7d704 | 120 | ;; |
fe52c5e0 MT |
121 | -*) |
122 | log WARNING "Unrecognized argument: ${1}" | |
123 | ;; | |
124 | *) | |
125 | chain=${1} | |
afb7d704 MT |
126 | ;; |
127 | esac | |
fe52c5e0 MT |
128 | shift |
129 | done | |
afb7d704 | 130 | |
fe52c5e0 MT |
131 | assert isset chain |
132 | assert isset table | |
133 | assert isoneof policy ACCEPT DROP "-" | |
2239db91 | 134 | |
fe52c5e0 | 135 | iptables "${protocol}" -t "${table}" ":${chain} ${policy} [0:0]" |
afb7d704 MT |
136 | } |
137 | ||
138 | # Calls the binary iptables command. | |
1c6a4e30 | 139 | _iptables() { |
fe52c5e0 MT |
140 | local protocol="${1}" |
141 | assert isset protocol | |
142 | shift | |
afb7d704 | 143 | |
fe52c5e0 MT |
144 | local cmd |
145 | case "${protocol}" in | |
afb7d704 | 146 | ipv6) |
fe52c5e0 | 147 | cmd="ip6tables" |
afb7d704 MT |
148 | ;; |
149 | ipv4) | |
fe52c5e0 | 150 | cmd="iptables" |
afb7d704 MT |
151 | ;; |
152 | esac | |
fe52c5e0 MT |
153 | assert isset cmd |
154 | cmd="$(which ${cmd})" | |
155 | ||
156 | cmd "${cmd}" "$@" | |
157 | return $? | |
158 | } | |
159 | ||
1c6a4e30 | 160 | iptables_status() { |
fe52c5e0 MT |
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 | |
afb7d704 | 170 | |
afb7d704 MT |
171 | return ${EXIT_OK} |
172 | } | |
173 | ||
1c6a4e30 | 174 | iptables_rulesfile() { |
afb7d704 MT |
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}" | |
98146c00 MT |
182 | } |
183 | ||
1c6a4e30 | 184 | iptables_init() { |
fe52c5e0 MT |
185 | local protocol="${1}" |
186 | assert isset protocol | |
187 | ||
188 | local policy="${2}" | |
afb7d704 | 189 | assert isset policy |
98146c00 | 190 | |
afb7d704 | 191 | # Create filter table and initialize chains. |
fe52c5e0 MT |
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" | |
98146c00 MT |
210 | } |
211 | ||
afb7d704 | 212 | # Load the created ruleset into the kernel. |
1c6a4e30 | 213 | iptables_commit () { |
fe52c5e0 MT |
214 | local protocol="${1}" |
215 | assert isset protocol | |
216 | shift | |
98146c00 | 217 | |
fe52c5e0 | 218 | local testmode="false" |
98146c00 | 219 | |
fe52c5e0 MT |
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 | |
16ac9775 | 231 | |
fe52c5e0 MT |
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 | ;; | |
16ac9775 | 252 | |
fe52c5e0 MT |
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 | ;; | |
16ac9775 | 259 | |
fe52c5e0 MT |
260 | 1,${EXIT_OK}) |
261 | log DEBUG "Ruleset successfully loaded (${protocol})" | |
262 | return ${EXIT_OK} | |
263 | ;; | |
afb7d704 | 264 | |
fe52c5e0 MT |
265 | 1,*) |
266 | log CRITICAL "Ruleset loading failed (${protocol})" | |
267 | return ${ret} | |
268 | ;; | |
269 | esac | |
afb7d704 | 270 | |
fe52c5e0 MT |
271 | # Skip the second loop iteration, if we are running in test mode. |
272 | enabled testmode && break | |
afb7d704 | 273 | |
fe52c5e0 | 274 | load_cmd="" |
afb7d704 MT |
275 | done |
276 | ||
fe52c5e0 MT |
277 | return ${EXIT_OK} |
278 | } | |
afb7d704 | 279 | |
1c6a4e30 | 280 | _iptables_commit_cat_rulesfile() { |
fe52c5e0 MT |
281 | local protocol="${1}" |
282 | assert isset protocol | |
afb7d704 | 283 | |
fe52c5e0 MT |
284 | local rulesfile="${2}" |
285 | assert isset rulesfile | |
afb7d704 | 286 | |
fe52c5e0 MT |
287 | local table |
288 | local file | |
289 | for table in ${IPTABLES_TABLES}; do | |
290 | file="${IPTABLES_TMPDIR}/${protocol}-${table}" | |
afb7d704 | 291 | |
fe52c5e0 | 292 | fread "${file}" |
afb7d704 | 293 | |
fe52c5e0 MT |
294 | # Add the COMMIT statement for every table. |
295 | print "COMMIT" | |
296 | done > "${rulesfile}" | |
297 | ||
298 | assert [ -s "${rulesfile}" ] | |
afb7d704 MT |
299 | } |
300 | ||
1c6a4e30 | 301 | _iptables_commit_load_rulesfile() { |
fe52c5e0 MT |
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 | |
98146c00 | 318 | |
fe52c5e0 MT |
319 | local iptables_cmd |
320 | case "${protocol}" in | |
afb7d704 | 321 | ipv6) |
fe52c5e0 | 322 | iptables_cmd="ip6tables-restore" |
afb7d704 MT |
323 | ;; |
324 | ipv4) | |
fe52c5e0 | 325 | iptables_cmd="iptables-restore" |
afb7d704 MT |
326 | ;; |
327 | esac | |
fe52c5e0 | 328 | assert isset iptables_cmd |
afb7d704 MT |
329 | |
330 | if enabled testmode; then | |
fe52c5e0 | 331 | list_append iptables_cmd "--test" |
afb7d704 MT |
332 | fi |
333 | ||
fe52c5e0 MT |
334 | # Save when importing the rules has started. |
335 | local time_started="$(timestamp)" | |
afb7d704 | 336 | |
fe52c5e0 | 337 | cmd "${iptables_cmd}" < "${rulesfile}" |
afb7d704 MT |
338 | local ret=$? |
339 | ||
340 | case "${ret}" in | |
341 | ${EXIT_OK}) | |
fe52c5e0 MT |
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!" | |
afb7d704 MT |
348 | ;; |
349 | *) | |
350 | if ! enabled testmode; then | |
fe52c5e0 | 351 | log CRITICAL "Error loading firewall ruleset for ${protocol}!" |
afb7d704 MT |
352 | fi |
353 | ;; | |
354 | esac | |
355 | ||
356 | return ${ret} | |
357 | } | |
358 | ||
1c6a4e30 | 359 | iptables_dump() { |
fe52c5e0 MT |
360 | local protocol="${1}" |
361 | assert isset protocol | |
afb7d704 | 362 | |
fe52c5e0 MT |
363 | local rulesfile="${2}" |
364 | assert isset rulesfile | |
365 | shift 2 | |
98146c00 | 366 | |
fe52c5e0 | 367 | local log_facility="INFO" |
98146c00 | 368 | |
afb7d704 MT |
369 | while [ $# -gt 0 ]; do |
370 | case "${1}" in | |
fe52c5e0 | 371 | --log-facility=*) |
2212045f | 372 | log_facility="$(cli_get_val "${1}")" |
afb7d704 MT |
373 | ;; |
374 | *) | |
fe52c5e0 | 375 | log WARNING "Unrecognized argument: ${1}" |
afb7d704 MT |
376 | ;; |
377 | esac | |
378 | shift | |
379 | done | |
380 | ||
fe52c5e0 MT |
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 ))" | |
98146c00 | 388 | |
fe52c5e0 MT |
389 | printf -v line "%4d | %s" "${counter}" "${line}" |
390 | log "${log_facility}" "${line}" | |
391 | done < "${rulesfile}" | |
98146c00 MT |
392 | } |
393 | ||
1c6a4e30 | 394 | iptables_LOG() { |
4320067c | 395 | local prefix="${1}" |
afb7d704 | 396 | local ret |
98146c00 | 397 | |
4320067c MT |
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 | ||
afb7d704 MT |
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}" | |
98146c00 MT |
426 | } |
427 | ||
1c6a4e30 | 428 | iptables_protocol() { |
98146c00 MT |
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 | ||
1c6a4e30 | 443 | _iptables_port_range() { |
2212045f | 444 | grep -q ":" <<< "$@" |
98146c00 MT |
445 | } |
446 | ||
1c6a4e30 | 447 | _iptables_port_multiport() { |
2212045f | 448 | grep -q "," <<< "$@" |
98146c00 MT |
449 | } |
450 | ||
1c6a4e30 | 451 | _iptables_port() { |
98146c00 MT |
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 | ||
1c6a4e30 | 461 | iptables_source_port() { |
98146c00 MT |
462 | [ -z "$@" ] && return |
463 | local type | |
2212045f | 464 | type=$(_iptables_port "$@") |
98146c00 MT |
465 | if [ "$type" = "$IPTABLES_MULTIPORT" ]; then |
466 | echo "-m multiport --source-ports $@" | |
467 | else | |
468 | echo "--sport $@" | |
469 | fi | |
470 | } | |
471 | ||
1c6a4e30 | 472 | iptables_destination_port() { |
98146c00 MT |
473 | [ -z "$@" ] && return |
474 | local type | |
2212045f | 475 | type=$(_iptables_port "$@") |
98146c00 MT |
476 | if [ "$type" = "$IPTABLES_MULTIPORT" ]; then |
477 | echo "-m multiport --destination-ports $@" | |
478 | else | |
479 | echo "--dport $@" | |
480 | fi | |
481 | } |