mirror of
https://github.com/firehol/firehol.git
synced 2024-06-16 03:58:22 +00:00
12333 lines
354 KiB
Bash
Executable File
12333 lines
354 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# FireHOL - A firewall for humans...
|
|
#
|
|
# Copyright
|
|
#
|
|
# Copyright (C) 2002-2017 Costa Tsaousis <costa@tsaousis.gr>
|
|
# Copyright (C) 2012-2017 Phil Whineray <phil@sanewall.org>
|
|
#
|
|
# License
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# See the file COPYING for details.
|
|
#
|
|
READLINK_CMD=${READLINK_CMD:-readlink}
|
|
BASENAME_CMD=${BASENAME_CMD:-basename}
|
|
DIRNAME_CMD=${DIRNAME_CMD:-dirname}
|
|
function realdir {
|
|
local r="$1"; local t=$($READLINK_CMD "$r")
|
|
while [ "$t" ]; do
|
|
r=$(cd $($DIRNAME_CMD "$r") && cd $($DIRNAME_CMD "$t") && pwd -P)/$($BASENAME_CMD "$t")
|
|
t=$($READLINK_CMD "$r")
|
|
done
|
|
$DIRNAME_CMD "$r"
|
|
}
|
|
PROGRAM_FILE="$0"
|
|
PROGRAM_DIR="${FIREHOL_OVERRIDE_PROGRAM_DIR:-$(realdir "$0")}"
|
|
PROGRAM_PWD="${PWD}"
|
|
declare -a PROGRAM_ORIGINAL_ARGS=("${@}")
|
|
|
|
# Services API version
|
|
FIREHOL_SERVICES_API="1"
|
|
|
|
for functions_file in install.config functions.common services.common services.firehol
|
|
do
|
|
if [ -r "$PROGRAM_DIR/$functions_file" ]
|
|
then
|
|
source "$PROGRAM_DIR/$functions_file"
|
|
else
|
|
1>&2 echo "Cannot access $PROGRAM_DIR/$functions_file"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
common_disable_localization || exit
|
|
common_private_umask || exit
|
|
common_require_root || exit
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# BITMASKED MARKS
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
|
|
declare -A MARKS_BITS=()
|
|
declare -A MARKS_MASKS=()
|
|
declare -A MARKS_MAX=()
|
|
declare -A MARKS_SHIFT=()
|
|
declare -A MARKS_SAVERESTORE=()
|
|
declare -A MARKS_STATEFUL=()
|
|
MARKS_SAVERESTORE_STATEFUL_MASK="0x00000000"
|
|
MARKS_SAVERESTORE_STATELESS_MASK="0x00000000"
|
|
MARKS_TOTAL_BITS=0
|
|
|
|
# taken from http://stackoverflow.com/questions/109023/how-to-count-the-number-of-set-bits-in-a-32-bit-integer
|
|
# and http://books.google.gr/books?id=iBNKMspIlqEC&pg=PA66&redir_esc=y#v=onepage&q&f=false
|
|
# counts the number of bits set in a number
|
|
numberofbits() {
|
|
local x="${1}"
|
|
|
|
x=$[ x - ( (x >> 1) & 0x55555555) ]
|
|
x=$[ (x & 0x33333333) + ( (x >> 2) & 0x33333333) ]
|
|
x=$[ (x + (x >> 4) ) & 0x0F0F0F0F ]
|
|
x=$[ x + (x >> 8) ]
|
|
x=$[ x + (x >> 16) ]
|
|
bits=$[ x & 0x0000003F ]
|
|
echo $bits
|
|
}
|
|
|
|
# taken from http://www.skorks.com/2010/10/write-a-function-to-determine-if-a-number-is-a-power-of-2/
|
|
# checks if a number is power of 2
|
|
ispoweroftwo() {
|
|
local num="${1}"
|
|
|
|
test ! $num = 0 -a $[num & (num - 1)] = 0 && return 0
|
|
return 1
|
|
}
|
|
|
|
marksreset() { markdef clear; }
|
|
markdef() {
|
|
if [ "$1" = "reset" -o "$1" = "clear" ]
|
|
then
|
|
MARKS_BITS=()
|
|
MARKS_MASKS=()
|
|
MARKS_MAX=()
|
|
MARKS_SHIFT=()
|
|
MARKS_SAVERESTORE=()
|
|
MARKS_STATEFUL=()
|
|
MARKS_SAVERESTORE_STATEFUL_MASK="0x00000000"
|
|
MARKS_SAVERESTORE_STATELESS_MASK="0x00000000"
|
|
MARKS_TOTAL_BITS=0
|
|
return 0
|
|
fi
|
|
|
|
local saverestore=1
|
|
local stateful=1
|
|
local mask=
|
|
local name="${1}"
|
|
local max="${2}"
|
|
shift 2
|
|
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1}" in
|
|
default)
|
|
saverestore=1
|
|
stateful=1
|
|
;;
|
|
|
|
classic)
|
|
saverestore=0
|
|
stateful=0
|
|
;;
|
|
|
|
save|restore|permanent)
|
|
saverestore=1
|
|
;;
|
|
|
|
nosave|norestore|temp|temporary)
|
|
saverestore=0
|
|
;;
|
|
|
|
stateless)
|
|
stateful=0
|
|
;;
|
|
|
|
stateful)
|
|
stateful=1
|
|
;;
|
|
|
|
*)
|
|
echo >&2 "ERROR in ${FUNCNAME}: Unknown keyword '${1}'."
|
|
exit 1
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
if [ ! -z "${MARKS_MASKS[$name]}" ]
|
|
then
|
|
echo >&2 "ERROR in ${FUNCNAME}: Mark type '${name}' already exists with mask ${MARKS_MASKS[$name]}. Please use 'markdef reset' to reset them before re-defining them."
|
|
exit 1
|
|
fi
|
|
|
|
if [ "${max}" = "rest" ]
|
|
then
|
|
max=$[ 1 << (32 - MARKS_TOTAL_BITS) ]
|
|
fi
|
|
|
|
if ! ispoweroftwo $max
|
|
then
|
|
echo >&2 "ERROR in ${FUNCNAME}: Max value $max of mark '$name' is not a power of 2."
|
|
exit 1
|
|
fi
|
|
|
|
# it will be from 0 to max - 1
|
|
max=$[ max - 1 ]
|
|
|
|
if [ $max -lt 1 -o $max -gt $[ 0xffffffff ] ]
|
|
then
|
|
echo >&2 "ERROR in ${FUNCNAME}: Max value $max of mark '$name' is out of bounds."
|
|
exit 1
|
|
fi
|
|
|
|
bits=$(numberofbits $max)
|
|
|
|
if [ $bits -eq 0 ]
|
|
then
|
|
echo >&2 "ERROR in ${FUNCNAME}: INTERNAL ERROR: Cannot figure out the bits set of value $max."
|
|
exit 1
|
|
fi
|
|
|
|
if [ $[ bits + MARKS_TOTAL_BITS ] -gt 32 ]
|
|
then
|
|
echo >&2 "ERROR in ${FUNCNAME}: Too many masks were requested. Cannot proceed. Please use fewer."
|
|
exit 1
|
|
fi
|
|
|
|
# find its mask
|
|
# we have all the bits we need set in $mark
|
|
# just shift it to the right position.
|
|
mask=$[ max << MARKS_TOTAL_BITS ]
|
|
|
|
MARKS_SHIFT[$name]=${MARKS_TOTAL_BITS}
|
|
MARKS_MAX[$name]=$max
|
|
MARKS_BITS[$name]=$bits
|
|
MARKS_MASKS[$name]=$(printf "0x%08x" $mask)
|
|
MARKS_STATEFUL[$name]=$stateful
|
|
MARKS_SAVERESTORE[$name]=$saverestore
|
|
|
|
if [ $saverestore -eq 1 ]
|
|
then
|
|
if [ $stateful -eq 1 ]
|
|
then
|
|
MARKS_SAVERESTORE_STATEFUL_MASK=$(printf "0x%08x" $[MARKS_SAVERESTORE_STATEFUL_MASK | mask])
|
|
else
|
|
MARKS_SAVERESTORE_STATELESS_MASK=$(printf "0x%08x" $[MARKS_SAVERESTORE_STATELESS_MASK | mask])
|
|
fi
|
|
fi
|
|
MARKS_TOTAL_BITS=$[ MARKS_TOTAL_BITS + bits ]
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# GLOBAL DEFAULTS
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# --- BEGIN OF FIREHOL DEFAULTS ---
|
|
|
|
# These are the defaults for FireHOL.
|
|
# You can set everything system-wide here, or set any or all
|
|
# of these to your firewall config file.
|
|
# The options set in the firewall config file have the highest
|
|
# priority (will overwrite these one).
|
|
|
|
# FireHOL config directory.
|
|
# EVEN IF YOU CHANGE THIS, THE firehol-defaults.conf FILE
|
|
# SHOULD STILL EXIST IN THE ORIGINAL $SYSCONFDIR/firehol
|
|
FIREHOL_CONFIG_DIR="${FIREHOL_CONFIG_DIR}"
|
|
|
|
# FireHOL services directory.
|
|
# FireHOL will look into this directory for service
|
|
# definition files (*.conf).
|
|
# Package maintainers may install their service definitions
|
|
# in this directory.
|
|
# Default: $SYSCONFDIR/firehol/services
|
|
FIREHOL_SERVICES_DIR="${FIREHOL_SERVICES_DIR}"
|
|
|
|
# Where to permanently save state information?
|
|
# Default: /var/spool/firehol
|
|
FIREHOL_SPOOL_DIR="${FIREHOL_SPOOL_DIR}"
|
|
|
|
# Where temporary files should go?
|
|
FIREHOL_RUN_DIR="${FIREHOL_RUN_DIR}"
|
|
|
|
# show a spinner during processing that shows
|
|
# number of iptables statements generated
|
|
FIREHOL_ENABLE_SPINNER=${FIREHOL_ENABLE_SPINNER-0}
|
|
|
|
# Restore instead of Start when possible.
|
|
# If set to 1, FireHOL will actually do a 'restore' when a
|
|
# 'start' is requested.
|
|
# If enabled and the config files have not changed since
|
|
# the last successful activation, the last successfully
|
|
# activated firewall will be restored.
|
|
# THIS OPTION SHOULD NOT BE ENABLED IF THE FIREWALL CONFIG
|
|
# IS USING DYNAMIC DETECTION OF SERVER PORTS OR OTHER DATA
|
|
# THAT MAY INFLUENCE THE GENERATED RULES.
|
|
# At the other hand, if the firewall is always static
|
|
# this option provides fast startup of the firewall.
|
|
# Default: 0
|
|
FIREHOL_RESTORE_INSTEAD_OF_START="0"
|
|
|
|
# Enable IPv4 firewall
|
|
# Default: 1
|
|
ENABLE_IPV4="${ENABLE_IPV4-1}"
|
|
|
|
# Enable IPv6 firewall
|
|
# Default: 1
|
|
ENABLE_IPV6="${ENABLE_IPV6-1}"
|
|
|
|
# Syslog facility to use when logging FireHOL events.
|
|
# This is only used by FireHOL, not the iptables packet
|
|
# logging mechanism.
|
|
# Default: daemon
|
|
FIREHOL_SYSLOG_FACILITY="daemon"
|
|
|
|
# FireHOL can wait for an interface to come up.
|
|
# Set the interface name to wait for, here.
|
|
# Default: check the environment variable, if any
|
|
WAIT_FOR_IFACE="${WAIT_FOR_IFACE}"
|
|
|
|
# External program to call on 'start' (successful or
|
|
# failed), 'stop' and 'panic'
|
|
# It will be run like this:
|
|
# "${FIREHOL_NOTIFICATION_PROGRAM}" "${FIREHOL_CONFIG}" "${result}" "${restored}" "${work_error}" "${work_runtime_error}"
|
|
# where
|
|
# FIREHOL_CONFIG is the filename of the config
|
|
# result is either empty, OK or FAILED
|
|
# restored is either NO, OK or FAILED
|
|
# work_error is the count of pre-processing errors encountered
|
|
# work_runtime_error is the count of post-processing errors encountered
|
|
# Default: check the environment variable, if any
|
|
FIREHOL_NOTIFICATION_PROGRAM="${FIREHOL_NOTIFICATION_PROGRAM}"
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# RUNTIME CONTROL VARIABLES
|
|
|
|
# These do not affect the final firewall output. They just control how
|
|
# FireHOL behaves.
|
|
# They can also be set as environment variables of the same name.
|
|
|
|
# If set to 1, FireHOL will attempt to activate the firewall with
|
|
# iptables-restore. This is a lot faster firewall activation.
|
|
# The only drawback of this, is that in case of error, FireHOL may be unable to
|
|
# identify the exact statement in the firewall config that caused the error.
|
|
# Default: 1
|
|
FIREHOL_FAST_ACTIVATION="${FIREHOL_FAST_ACTIVATION-1}"
|
|
|
|
# Only when FIREHOL_FAST_ACTIVATION=1, this value is the time in seconds, to
|
|
# wait for just an ENTER before trying the new firewall.
|
|
FIREHOL_WAIT_USER_BEFORE_TRY=600
|
|
|
|
# If set to 0, firehol will not try to load the required kernel modules
|
|
# Generally, FireHOL is able to detect if a module is compiled in the kernel,
|
|
# even if this is set to 1.
|
|
# Default: 1
|
|
FIREHOL_LOAD_KERNEL_MODULES="${FIREHOL_LOAD_KERNEL_MODULES-1}"
|
|
|
|
# Firewall Policy during firewall activation
|
|
# Default: ACCEPT
|
|
# Possible values: ACCEPT, REJECT, DROP
|
|
FIREHOL_INPUT_ACTIVATION_POLICY="${FIREHOL_INPUT_ACTIVATION_POLICY-ACCEPT}"
|
|
FIREHOL_OUTPUT_ACTIVATION_POLICY="${FIREHOL_OUTPUT_ACTIVATION_POLICY-ACCEPT}"
|
|
FIREHOL_FORWARD_ACTIVATION_POLICY="${FIREHOL_FORWARD_ACTIVATION_POLICY-ACCEPT}"
|
|
|
|
# Do we allow pre-existing connections to continue during activation?
|
|
# If this is set to 0 and FIREHOL_FAST_ACTIVATION is also set to 0, then every
|
|
# time the firewall is activated, existing connections will be disrupted.
|
|
# Default: 1
|
|
FIREHOL_ESTABLISHED_ACTIVATION_ACCEPT="${FIREHOL_ESTABLISHED_ACTIVATION_ACCEPT-1}"
|
|
|
|
# If you want to restore the firewall using the iptables init script of
|
|
# your distribution, set here the paths where it expects the rules.
|
|
# These settings are only saved when 'save' is requested at the command line.
|
|
# Default: unset for automatic detection.
|
|
FIREHOL_AUTOSAVE="${FIREHOL_AUTOSAVE}"
|
|
FIREHOL_AUTOSAVE6="${FIREHOL_AUTOSAVE6}"
|
|
|
|
# Ready to use values for various distributions:
|
|
#
|
|
# Gentoo
|
|
# Check: /etc/conf.d/iptables and ip6tables
|
|
#FIREHOL_AUTOSAVE="/var/lib/iptables/rules-save"
|
|
#FIREHOL_AUTOSAVE6="/var/lib/ip6tables/rules-save"
|
|
#
|
|
# Arch
|
|
# Check: /usr/lib/systemd/system/iptables.service and ip6tables.service
|
|
#FIREHOL_AUTOSAVE=/etc/iptables/iptables.rules
|
|
#FIREHOL_AUTOSAVE6=/etc/iptables/ip6tables.rules
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# FIREWALL CONFIGURATION VARIABLES
|
|
|
|
# These affect the final firewall output.
|
|
# They can also be set in the firewall config file.
|
|
|
|
# This controls how 'optimal' or 'accurate' the iptables statements
|
|
# generated will be.
|
|
# 'optimal' generates a production state firewall optimized for speed.
|
|
# It makes FireHOL accept all packets of ESTABLISHED connections at the
|
|
# beginning of the firewall, thus practically eliminating the need for
|
|
# filtering ESTABLISHED traffic. Packet filtering is only done for NEW
|
|
# sockets.
|
|
# 'accurate' generates a firewall that precisely matches all packets in both
|
|
# directions: client->server and server->client
|
|
# This setting affects logging, accounting and stateless rules.
|
|
# When it is set to 'optimal', logging will only log the first packet of NEW
|
|
# sockets, accounting will only account the first packet of NEW sockets and
|
|
# stateless rules are disabled.
|
|
# When it is set to 'accurate', logging and accounting will be done for all
|
|
# packets and stateless rules are enabled.
|
|
# Default: accurate
|
|
# Possible Values: optimal accurate
|
|
FIREHOL_RULESET_MODE="accurate"
|
|
|
|
# Should we drop all INVALID packets always?
|
|
# INVALID packets as seen by the connection tracker.
|
|
# Check also the next section (SYSTEM CONFIGURATION) for related options.
|
|
# It can be enabled per interface using 'protection invalid'.
|
|
# This will be enabled if you use the synproxy helper.
|
|
# Default: 1
|
|
FIREHOL_DROP_INVALID=1
|
|
|
|
# When FIREHOL_DROP_INVALID=1 shall we also log the dropped packets?
|
|
# Default: 1
|
|
FIREHOL_LOG_DROP_INVALID=1
|
|
|
|
# the action to be performed when we drop INVALID packets
|
|
FIREHOL_DROP_INVALID_ACTION="DROP"
|
|
|
|
# If set to 1, FireHOL will accept unmatched outbound RST packets.
|
|
# This is needed when the firewall is expected to reject orphan
|
|
# routed connections.
|
|
# Default: 0
|
|
FIREHOL_ACCEPT_OUTPUT_UNMATCHED_TCP_RST=0
|
|
|
|
# If set to 1, FireHOL will silently drop orphan TCP packets with ACK,FIN set.
|
|
# In modern kernels, the connection tracker detects closed sockets
|
|
# and removes them from memory before receiving the FIN,ACK from the remote
|
|
# party. This makes FireHOL log these packets when they will be received.
|
|
# To silently drop these packets, enable this option.
|
|
# Default: 1
|
|
FIREHOL_DROP_ORPHAN_TCP_ACK_FIN=1
|
|
|
|
# If set to 1, FireHOL will silently drop orphan TCP packets with ACK,RST set.
|
|
# In modern kernels, the connection tracker detects closed sockets
|
|
# and removes them from memory before receiving the ACK,RST from the remote
|
|
# party. This makes FireHOL log these packets when they will be received.
|
|
# To silently drop these packets, enable this option.
|
|
# Default: 1
|
|
FIREHOL_DROP_ORPHAN_TCP_ACK_RST=1
|
|
|
|
# If set to 1, FireHOL will silently drop orphan TCP packets with ACK set.
|
|
# In modern kernels, the connection tracker detects closed sockets
|
|
# and removes them from memory before receiving the ACK from the remote
|
|
# party. This makes FireHOL log these packets when they will be received.
|
|
# To silently drop these packets, enable this option.
|
|
# Default: 1
|
|
FIREHOL_DROP_ORPHAN_TCP_ACK=1
|
|
|
|
# If set to 1, FireHOL will silently drop orphan TCP packets with RST set.
|
|
# In modern kernels, the connection tracker detects closed sockets
|
|
# and removes them from memory before receiving the RST from the remote
|
|
# party. This makes FireHOL log these packets when they will be received.
|
|
# To silently drop these packets, enable this option.
|
|
# Default: 1
|
|
FIREHOL_DROP_ORPHAN_TCP_RST=1
|
|
|
|
# If set to 1, FireHOL will silently drop orphan ICMP destination unreachable
|
|
# packets. In modern kernels, the connection tracker detects closed sockets
|
|
# and removes them from memory before receiving the ICMP destination
|
|
# unreachable from the remote party. This makes FireHOL log these packets when
|
|
# they will be received.
|
|
# To silently drop these packets, enable this option.
|
|
# Default: 1
|
|
FIREHOL_DROP_ORPHAN_IPV4_ICMP_TYPE3=1
|
|
|
|
# If enabled FireHOL will create a chain per server/client statement and jump
|
|
# to it from the main flow. Normally this jump is only required for clarity.
|
|
# So it is advised to leave this setting to 0, for a more efficient firewall.
|
|
# Default: 0
|
|
FIREHOL_CHAIN_PER_SERVICE=0
|
|
|
|
# If enabled, FireHOL will use the multiport matches of iptables, when
|
|
# possible. Note that multiport matches in iptables do not support matching
|
|
# both source and destination ports on the same statement, with different
|
|
# ports for source and destination.
|
|
# So, FireHOL will only use it if sport or dport in set to 'any' (or not at
|
|
# all) or when sport and dport are the exactly the same ports.
|
|
# Default: 1
|
|
FIREHOL_SUPPORT_MULTIPORT=1
|
|
|
|
# There are matches that are more "expensive" than others. When enabled,
|
|
# FireHOL will use branching (create a new chain and jump to it) in order to
|
|
# avoid executing expensive matches more than once per statement.
|
|
# Expensive matches are: limit, connlimit, ipset (not when used in src/dst),
|
|
# helpers (for RELATED matches).
|
|
# Default: 1
|
|
FIREHOL_PROTECTED_MATCHES=1
|
|
|
|
# How to configure conntrack helper assignment?
|
|
#
|
|
# 'kernel' = the kernel will attempt to match RELATED sockets for all
|
|
# conntrack helpers and all traffic matching its predefined rules.
|
|
# This is considered a security threat and should be avoided.
|
|
# Check: https://home.regit.org/netfilter-en/secure-use-of-helpers/
|
|
#
|
|
# 'firehol' = FireHOL will generate rules in the 'raw' table, using the -j CT
|
|
# target of iptables to match the flows of the statements
|
|
# the conntrack helpers are used.
|
|
# CAUTION: FireHOL generated statements are not NAT aware.
|
|
# You should only use this, if you don't NAT traffic that have to
|
|
# be seen by conntrack helpers to detect RELATED ports.
|
|
#
|
|
# 'manual' = You configure conntrack helper assignment manually using
|
|
# the 'cthelper' firehol helper.
|
|
# In this case, neither the kernel nor FireHOL will do anything
|
|
# about conntrack helpers assignment.
|
|
#
|
|
# Default: kernel
|
|
FIREHOL_CONNTRACK_HELPERS_ASSIGNMENT="kernel"
|
|
|
|
# SYNPROXY options
|
|
# The options to pass to -j SYNPROXY
|
|
# Default: --sack-perm --timestamp --wscale 7 --mss 1460
|
|
FIREHOL_SYNPROXY_OPTIONS="--sack-perm --timestamp --wscale 7 --mss 1460"
|
|
|
|
# Shall the SYNPROXY FireHOL helper log the packets as they pass during
|
|
# the various phases?
|
|
# Default: 0
|
|
FIREHOL_SYNPROXY_LOG=0
|
|
|
|
# Shall the dnat helper log the packets as they pass during?
|
|
FIREHOL_NAT_HELPER_LOG=0
|
|
|
|
# When enabled, synproxy will exclude for the SYNPROXY->SERVER match
|
|
# all the packets that have an UID or a GID (ownmer matches).
|
|
# When enabled, dst is not required for synproxy and synproxy can
|
|
# be used on dynamic IPs (it requires an 'src not; though).
|
|
# Default: 0
|
|
FIREHOL_SYNPROXY_EXCLUDE_OWNER=0
|
|
|
|
# Trust loopback?
|
|
# loose = Trust device lo unconditionally
|
|
# strict = Trust only IPs 127.0.0.0/8 and ::1 on device lo
|
|
# Default: loose
|
|
FIREHOL_TRUST_LOOPBACK="loose"
|
|
|
|
# If set to non-empty, FireHOL will apply a global reverse filtering on all
|
|
# traffic. If you use connection tracker helpers, you should enable this.
|
|
# Check: https://home.regit.org/netfilter-en/secure-use-of-helpers/
|
|
# Default: <empty>
|
|
# Possible Values: <check: iptables -m rpfilter --help>
|
|
FIREHOL_GLOBAL_RPFILTER=
|
|
|
|
# The default policy for the interfaces of the firewall. This can be
|
|
# controlled on a per interface basis using the policy interface subcommand.
|
|
# Default: DROP
|
|
# Possible Values: DROP REJECT RETURN or custom action
|
|
DEFAULT_INTERFACE_POLICY="DROP"
|
|
|
|
# The default policy for the router commands of the firewall. This can be
|
|
# controlled on a per interface basis using the policy interface subscommand.
|
|
# Default: RETURN
|
|
# Possible Values: DROP REJECT RETURN or custom action
|
|
DEFAULT_ROUTER_POLICY="RETURN"
|
|
|
|
# At the end of the firewall, there may be packets not matched
|
|
# anywhere. What to do with them?
|
|
# Default: DROP
|
|
# Possible Values: DROP REJECT
|
|
UNMATCHED_INPUT_POLICY="DROP"
|
|
UNMATCHED_OUTPUT_POLICY="DROP"
|
|
UNMATCHED_ROUTER_POLICY="DROP"
|
|
|
|
# The client ports to be used for "default" client ports when the
|
|
# client specified is a foreign host.
|
|
# Note that FireHOL will ask the kernel for default client ports of
|
|
# the local host. This setting only applies to client ports of remote hosts.
|
|
# Default: 1024:65535
|
|
DEFAULT_CLIENT_PORTS="1024:65535"
|
|
|
|
# The time to track persistence manually, when the kernel cannot do it.
|
|
FIREHOL_NAT_PERSISTENCE_SECONDS="3600"
|
|
|
|
# ----------------------------------------------------------------------
|
|
# SYSTEM CONFIGURATION
|
|
|
|
# Set this to 1 have firehol load NAT kernel modules
|
|
# It will be enabled automatically if nat commands are given in the firewall.
|
|
# Default: 0
|
|
FIREHOL_NAT="${FIREHOL_NAT-0}"
|
|
|
|
# Set this to 1 to enable routing of packets in the kernel
|
|
# It will be enabled automatically if routers are defined in the firewall.
|
|
# Default: 0
|
|
FIREHOL_ROUTING="${FIREHOL_ROUTING-0}"
|
|
|
|
# Make connection tracker use strick categorization of TCP packets.
|
|
# When set to zero the packets that do not seem right will be marked as INVALID
|
|
# instead of NEW. It will also improve ACK flood protection.
|
|
# This sets net.netfilter.nf_conntrack_tcp_loose=0
|
|
# The system default is 1, the firehol default is 0.
|
|
# Default: <unset to not set it>
|
|
FIREHOL_CONNTRACK_LOOSE_MATCHING=
|
|
|
|
# How many connection tracking sockets are supported?
|
|
# The system default is 65536.
|
|
# On busy servers you may need to increase this.
|
|
# Keep in mind that each socket in the connection tracker takes 288 bytes.
|
|
# Default: <unset to not set it>
|
|
FIREHOL_CONNTRACK_MAX=
|
|
|
|
# How many hash entries should the connection tracker have?
|
|
# The system default is 16384.
|
|
# On busy servers you may need to increase this. Keep in mind that each hash
|
|
# entry takes 8 bytes, but somehow this is related to your CPU L3 cache size.
|
|
# Default: <unset to not set it>
|
|
FIREHOL_CONNTRACK_HASHSIZE=
|
|
|
|
# required for synproxy
|
|
# This will be automatically set to 1 if you use the synproxy helper.
|
|
# This sets net.ipv4.tcp_syncookies=1
|
|
# Default: <unset to not set it>
|
|
FIREHOL_TCP_SYN_COOKIES=
|
|
|
|
# required for synproxy
|
|
# This will be automatically set to 1 if you use the synproxy helper.
|
|
# This sets net.ipv4.tcp_timestamps=1
|
|
# Default: <unset to not set it>
|
|
FIREHOL_TCP_TIMESTAMPS=
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# IPTABLES MARKS BITMASKING
|
|
|
|
# FireHOL allows multiple independent MARKs.
|
|
# By default FireHOL requires 'connmark' and 'usermark'.
|
|
|
|
# Mark types may be defined with this template:
|
|
#
|
|
# markdef NAME VALUES [stateful|stateless] [permanent|temporary]
|
|
#
|
|
# NAME = a name for this mark type
|
|
# connmark and usermark should always be defined.
|
|
#
|
|
# VALUES = max number of marks to support (0 to VALUES - 1)
|
|
# VALUES must be a power of two.
|
|
#
|
|
# stateful = all statements that assign this mark should
|
|
# only apply it on NEW packets.
|
|
#
|
|
# stateless = all statements that assign this mark type should
|
|
# only apply it only to traffic matched by the
|
|
# optional rule parameters given.
|
|
#
|
|
# temporary = do not save/restore to/from connection marks.
|
|
# This means RESPONSES to the matched packets
|
|
# will not get the mark.
|
|
#
|
|
# permanent = save/restore to/from connection marks
|
|
# This means that RESPONSES will get the mark.
|
|
#
|
|
# NOTES ABOUT markdef OPTIONS
|
|
#
|
|
# default is : stateful permanent or default
|
|
# in this mode, only NEW packets of connections need
|
|
# to be marked. ESTABLISHED and RELATED packets
|
|
# will automatically get the same mark too.
|
|
# So, in FireHOL mark helpers (connmark, mark, custommark)
|
|
# you will only need to match a REQUEST packet and
|
|
# automatically all the packets of the connection will
|
|
# get the mark.
|
|
#
|
|
# - stateful temporary
|
|
# In this mode, only NEW packets will be marked for each
|
|
# connection. ESTABLISHED and RELATED packets will NOT
|
|
# get the mark.
|
|
#
|
|
# - stateless permanent
|
|
# In this mode, whatever the helper statement matches
|
|
# will get the mark. This mark will also be applied to
|
|
# all the packets that are encountered after the marked
|
|
# packet and are part of the same socket.
|
|
#
|
|
# - stateless temporary or classic
|
|
# In this mode, only whatever the helper statement matches
|
|
# will get the mark. Nothing else.
|
|
#
|
|
|
|
# clear the internal marks - do not remove this line
|
|
markdef clear
|
|
|
|
# connmarks are used by the connmark helper
|
|
markdef connmark 64
|
|
|
|
# usermark are used by the mark helper
|
|
markdef usermark 128
|
|
|
|
# Custom mark example:
|
|
#
|
|
# markdef qosmark 8
|
|
#
|
|
# To use it use 'custommark' helper and optional rule parameter.
|
|
# The first argument to both should the mark name (qosmark in this case)
|
|
|
|
# ----------------------------------------------------------------------
|
|
# IPTABLES PACKETS LOGGING
|
|
|
|
# LOG mode for iptables
|
|
# Default: LOG
|
|
# Possible Values: LOG, ULOG, NFLOG
|
|
# LOG = syslog
|
|
# We recommend to install ulogd and use NFLOG.
|
|
FIREHOL_LOG_MODE="LOG"
|
|
|
|
# Accepts anything iptables accepts for each mode.
|
|
# Check: iptables -j LOG --help
|
|
# iptables -j ULOG --help
|
|
# iptables -j NFLOG --help
|
|
# Default: empty
|
|
FIREHOL_LOG_OPTIONS=""
|
|
|
|
# FireHOL can prefix each log with a keyword.
|
|
# Default: empty
|
|
FIREHOL_LOG_PREFIX=""
|
|
FIREHOL_LOG_ESCAPE="\""
|
|
|
|
# Used only for FIREHOL_LOG_MODE="LOG"
|
|
# The syslog level to be used when logging packets.
|
|
FIREHOL_LOG_LEVEL="warning"
|
|
|
|
# For loglimit, these are the frequency and the burst
|
|
# of logging. They are applied per logging rule, not across
|
|
# the firewall.
|
|
FIREHOL_LOG_FREQUENCY="1/second"
|
|
FIREHOL_LOG_BURST="5"
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# IPSET OPTIONS
|
|
|
|
# if set to zero, ipset restore does not support
|
|
# FLUSH SWAP DESTROY in which case they be executed during postprocess
|
|
IPSET_RESTORE_SUPPORTS_FLUSH_SWAP_DESTROY=1
|
|
|
|
# options that are appended to -m ipset matches when the ipset
|
|
# is used instead of src and dst IPs.
|
|
# The default is to prevent updating ipset counters
|
|
# Default: ! --update-counters ! --update-subcounters
|
|
IPSET_SRC_DST_OPTIONS="! --update-counters ! --update-subcounters"
|
|
|
|
# A recent ipset command uses these:
|
|
IPSET_CREATE_OPTION="create"
|
|
IPSET_DESTROY_OPTION="destroy"
|
|
IPSET_FLUSH_OPTION="flush"
|
|
IPSET_ADD_OPTION="add"
|
|
IPSET_DELETE_OPTION="del"
|
|
IPSET_SWAP_OPTION="swap"
|
|
IPSET_SAVE_OPTION="save"
|
|
IPSET_RESTORE_OPTION="-! restore"
|
|
IPSET_CREATE_IPV6_OPTION="family inet6"
|
|
IPSET_LIST_NAMES_EVAL="list -n"
|
|
|
|
# The default options to be passed to ipset
|
|
# when the iptrap helper creates the ipset
|
|
IPTRAP_DEFAULT_IPSET_TIMEOUT_OPTIONS="timeout 3600"
|
|
IPTRAP_DEFAULT_IPSET_COUNTERS_OPTIONS="timeout 3600 counters"
|
|
|
|
# ----------------------------------------------------------------------
|
|
# OLDER VERSIONS OF IPSET
|
|
|
|
# IPSET_RESTORE_SUPPORTS_FLUSH_SWAP_DESTROY=0
|
|
|
|
# older versions do not support the 'counters' option
|
|
# even older versions do not support the 'timeout' option
|
|
#IPTRAP_DEFAULT_IPSET_TIMEOUT_OPTIONS="timeout 3600"
|
|
#IPTRAP_DEFAULT_IPSET_COUNTERS_OPTIONS="" # not supported
|
|
|
|
# older versions use these
|
|
#IPSET_SRC_DST_OPTIONS=
|
|
#IPSET_CREATE_OPTION="-N"
|
|
#IPSET_DESTROY_OPTION="-X"
|
|
#IPSET_FLUSH_OPTION="-F"
|
|
#IPSET_ADD_OPTION="-A"
|
|
#IPSET_DELETE_OPTION="-D"
|
|
#IPSET_SAVE_OPTION="-S"
|
|
#IPSET_SWAP_OPTION="-W"
|
|
#IPSET_RESTORE_OPTION="-R"
|
|
#IPSET_CREATE_IPV6_OPTION="" # No ipv6 support
|
|
#IPSET_LIST_NAMES_EVAL="-L | grep Name: | cut -d: -f 2"
|
|
|
|
# ----------------------------------------------------------------------
|
|
# DEFAULT IP SETS
|
|
|
|
# FireHOL will overwrite these settings with the contents of the files with
|
|
# the same names in ${FIREHOL_CONFIG_DIR}.
|
|
#
|
|
# For example, RESERVED_IPV4 will be set from $SYSCONFDIR/firehol/RESERVED_IPV4
|
|
|
|
# IANA reserved address space that should never appear
|
|
RESERVED_IPV4="0.0.0.0/8 127.0.0.0/8 240.0.0.0/4 "
|
|
RESERVED_IPV6="::/8 0100::/8 0200::/7 0400::/6 0800::/5 1000::/4 4000::/3 6000::/3 8000::/3 A000::/3 C000::/3 E000::/4 F000::/5 F800::/6 FE00::/9 FEC0::/10"
|
|
|
|
# Private IPv4 address space
|
|
# 10.0.0.0/8 => RFC 1918: IANA Private Use
|
|
# 169.254.0.0/16 => Link Local
|
|
# 192.0.2.0/24 => Test Net
|
|
# 192.88.99.0/24 => RFC 3068: 6to4 anycast & RFC 2544: Benchmarking addresses
|
|
# 192.168.0.0/16 => RFC 1918: Private use
|
|
PRIVATE_IPV4="10.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.0.2.0/24 192.88.99.0/24 192.168.0.0/16"
|
|
|
|
# Private IPv6 address space
|
|
# FC00::/7 => Unique Local Unicast
|
|
# FE80::/10 => Link Local Unicast
|
|
PRIVATE_IPV6="FC00::/7 FE80::/10"
|
|
|
|
# The multicast address space
|
|
MULTICAST_IPV4="224.0.0.0/4"
|
|
MULTICAST_IPV6="FF00::/16"
|
|
|
|
# --- END OF FIREHOL DEFAULTS ---
|
|
|
|
# load the defaults if they exist
|
|
if [ -r "${FIREHOL_CONFIG_DIR}/firehol-defaults.conf" ]
|
|
then
|
|
source "${FIREHOL_CONFIG_DIR}/firehol-defaults.conf" || exit 1
|
|
fi
|
|
|
|
# default config file
|
|
FIREHOL_CONFIG="${FIREHOL_CONFIG_DIR}/firehol.conf"
|
|
|
|
# Concurrent run control
|
|
FIREHOL_LOCK_FILE="${FIREHOL_RUN_DIR}/firehol.lck"
|
|
|
|
# make sure the defaults include a connmark
|
|
if [ -z "${MARKS_MASKS[connmark]}" ]
|
|
then
|
|
echo >&2 "ERROR: File ${FIREHOL_CONFIG_DIR}/marks.conf does not define a 'connmark' definition."
|
|
exit 1
|
|
fi
|
|
|
|
# make sure the defaults include a usermark
|
|
if [ -z "${MARKS_MASKS[usermark]}" ]
|
|
then
|
|
echo >&2 "ERROR: File ${FIREHOL_CONFIG_DIR}/marks.conf does not define a 'usermark' definition."
|
|
exit 1
|
|
fi
|
|
|
|
# make sure we have a valid FIREHOL_RULESET_MODE
|
|
if [ ! "${FIREHOL_RULESET_MODE}" = "optimal" -a ! "${FIREHOL_RULESET_MODE}" = "accurate" ]
|
|
then
|
|
echo >&2 "ERROR: FIREHOL_RULESET_MODE can either be 'optimal' or 'accurate' but you set it as '${FIREHOL_RULESET_MODE}'."
|
|
exit 1
|
|
fi
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# EXTERNAL/SYSTEM COMMANDS MANAGEMENT
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
|
|
emit_version() {
|
|
${CAT_CMD} <<EOF
|
|
|
|
FireHOL $VERSION
|
|
(C) Copyright 2003-2015 Costa Tsaousis <costa@tsaousis.gr>
|
|
(C) Copyright 2012-2015 Phil Whineray <phil@firehol.org>
|
|
FireHOL is distributed under the GPL v2+.
|
|
Home Page: http://firehol.org
|
|
|
|
-------------------------------------------------------------------------
|
|
Get notified of new FireHOL releases by subscribing to the mailing list:
|
|
http://lists.firehol.org/mailman/listinfo/firehol-support/
|
|
-------------------------------------------------------------------------
|
|
|
|
EOF
|
|
}
|
|
|
|
RUNNING_ON_TERMINAL=0
|
|
if [ "z$1" = "z-nc" ]
|
|
then
|
|
shift
|
|
else
|
|
common_setup_terminal && RUNNING_ON_TERMINAL=1
|
|
fi
|
|
|
|
# disable the spinner when we don't run on a terminal
|
|
test ${RUNNING_ON_TERMINAL} -eq 0 && FIREHOL_ENABLE_SPINNER=0
|
|
|
|
FIREHOL_HAVE_IPRANGE=1
|
|
IPRANGE_WARNING=0
|
|
IPRANGE_REDUCE=Y
|
|
if [ ! -z "${IPRANGE_CMD}" ]
|
|
then
|
|
${IPRANGE_CMD} --has-reduce 2>/dev/null || IPRANGE_REDUCE=
|
|
fi
|
|
|
|
if [ -z "${IPRANGE_CMD}" -o -z "$IPRANGE_REDUCE" ]
|
|
then
|
|
FIREHOL_HAVE_IPRANGE=0
|
|
IPRANGE_WARNING=1
|
|
fi
|
|
|
|
ENABLE_ACCOUNTING=1
|
|
ACCOUNTING_WARNING=0
|
|
if [ -z "${NFACCT_CMD}" ]
|
|
then
|
|
# silently disable accounting here,
|
|
# the user will get a warning when the first
|
|
# accounting rule is evaluated
|
|
ENABLE_ACCOUNTING=0
|
|
ACCOUNTING_WARNING=1
|
|
fi
|
|
|
|
ENABLE_IPSET=1
|
|
IPSET_WARNING=0
|
|
if [ -z "${IPSET_CMD}" ]
|
|
then
|
|
# silently disable accounting here,
|
|
# the user will get a warning when the first
|
|
# accounting rule is evaluated
|
|
ENABLE_IPSET=0
|
|
IPSET_WARNING=1
|
|
fi
|
|
|
|
if [ ${ENABLE_IPV4} -eq 1 ]
|
|
then
|
|
if [ -z "${IPTABLES_CMD}" ]
|
|
then
|
|
echo >&2 " WARNING: no iptables command: IPv4 disabled"
|
|
ENABLE_IPV4=0
|
|
elif [ -z "${IPTABLES_SAVE_CMD}" ]
|
|
then
|
|
echo >&2 " WARNING: no iptables-save command: IPv4 disabled"
|
|
ENABLE_IPV4=0
|
|
elif [ -z "${IPTABLES_RESTORE_CMD}" ]
|
|
then
|
|
echo >&2 " WARNING: no iptables-restore command: IPv4 disabled"
|
|
ENABLE_IPV4=0
|
|
fi
|
|
fi
|
|
|
|
if [ ${ENABLE_IPV6} -eq 1 ]
|
|
then
|
|
if [ ! -f /proc/net/if_inet6 ]
|
|
then
|
|
# IPv6 not in use on this system, silently ignore
|
|
ENABLE_IPV6=0
|
|
elif [ -z "${IP6TABLES_CMD}" ]
|
|
then
|
|
echo >&2 " WARNING: no ip6tables command: IPv6 disabled"
|
|
ENABLE_IPV6=0
|
|
elif [ -z "${IP6TABLES_SAVE_CMD}" ]
|
|
then
|
|
echo >&2 " WARNING: no ip6tables-save command: IPv6 disabled"
|
|
ENABLE_IPV6=0
|
|
elif [ -z "${IP6TABLES_RESTORE_CMD}" ]
|
|
then
|
|
echo >&2 " WARNING: no ip6tables-restore command: IPv6 disabled"
|
|
ENABLE_IPV6=0
|
|
fi
|
|
fi
|
|
|
|
if [ ! ${FIREHOL_LOAD_KERNEL_MODULES} -eq 0 ]
|
|
then
|
|
if [ -z "${MODPROBE_CMD}" ]
|
|
then
|
|
echo >&2 " WARNING: no modprobe command: module loading disabled"
|
|
FIREHOL_LOAD_KERNEL_MODULES=0
|
|
fi
|
|
fi
|
|
|
|
firehol_concurrent_run_lock() {
|
|
exec 200>"${FIREHOL_LOCK_FILE}"
|
|
if [ $? -ne 0 ]; then exit; fi
|
|
${FLOCK_CMD} -n 200
|
|
if [ $? -ne 0 ]
|
|
then
|
|
echo >&2 "ERROR: FireHOL is already running. Exiting..."
|
|
exit 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Be nice on production environments
|
|
${RENICE_CMD} 10 $$ >/dev/null 2>/dev/null
|
|
|
|
# Initialize iptables
|
|
if [ $ENABLE_IPV4 -eq 1 ]
|
|
then
|
|
# there are 2 versions of iptables, regarding option -w
|
|
# -w wait for xlock
|
|
# -w SECS wait for xlock up to SECS seconds
|
|
# Unfortunately, they are mutually exclusive
|
|
|
|
${IPTABLES_CMD} -wnxvL >/dev/null 2>&1 || ${IPTABLES_CMD} -w 10 -nxvL >/dev/null 2>&1
|
|
if [ $? -ne 0 ]
|
|
then
|
|
echo >&2 " WARNING: error initializing iptables: IPv4 disabled"
|
|
ENABLE_IPV4=0
|
|
fi
|
|
fi
|
|
|
|
if [ $ENABLE_IPV6 -eq 1 ]
|
|
then
|
|
# there are 2 versions of iptables, regarding option -w
|
|
# -w wait for xlock
|
|
# -w SECS wait for xlock up to SECS seconds
|
|
# Unfortunately, they are mutually exclusive
|
|
|
|
${IP6TABLES_CMD} -wnxvL >/dev/null 2>&1 || ${IP6TABLES_CMD} -w 10 -nxvL >/dev/null 2>&1
|
|
if [ $? -ne 0 ]
|
|
then
|
|
echo >&2 " WARNING: error initializing ip6tables: IPv6 disabled"
|
|
ENABLE_IPV6=0
|
|
fi
|
|
fi
|
|
|
|
if [ $ENABLE_IPV4 -eq 0 -a $ENABLE_IPV6 -eq 0 ]
|
|
then
|
|
echo >&2 " ERROR: Neither IPv4 nor IPv6 is available - exiting"
|
|
exit 1
|
|
fi
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# GLOBAL PREPARATIONS
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# ----------------------------------------------------------------------
|
|
# Directories and files
|
|
|
|
if [ ! -d "${FIREHOL_RUN_DIR}" ]
|
|
then
|
|
${MKDIR_CMD} -p "${FIREHOL_RUN_DIR}" || exit 1
|
|
${CHOWN_CMD} root:root "${FIREHOL_RUN_DIR}" || exit 1
|
|
${CHMOD_CMD} 700 "${FIREHOL_RUN_DIR}" || exit 1
|
|
fi
|
|
|
|
if [ ! -d "${FIREHOL_SPOOL_DIR}" ]
|
|
then
|
|
${MKDIR_CMD} -p "${FIREHOL_SPOOL_DIR}" || exit 1
|
|
${CHOWN_CMD} root:root "${FIREHOL_SPOOL_DIR}" || exit 1
|
|
${CHMOD_CMD} 700 "${FIREHOL_SPOOL_DIR}" || exit 1
|
|
fi
|
|
|
|
# Create an empty temporary directory we need for this run.
|
|
if ! FIREHOL_DIR="`${MKTEMP_CMD} -d "${FIREHOL_RUN_DIR}/firehol-XXXXXXXXXX"`"
|
|
then
|
|
echo >&2
|
|
echo >&2
|
|
echo >&2 "ERROR: Cannot create temporary directory in ${FIREHOL_RUN_DIR}. Make sure you have a working mktemp."
|
|
echo >&2
|
|
exit 1
|
|
fi
|
|
#FIREHOL_CHAINS_DIR="${FIREHOL_DIR}/chains"
|
|
FIREHOL_OUTPUT="${FIREHOL_DIR}/firehol-out.sh"
|
|
FIREHOL_SAVED="${FIREHOL_DIR}/firehol-save.sh"
|
|
FIREHOL_SAVED6="${FIREHOL_DIR}/firehol-save6.sh"
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Make sure we automatically cleanup when we exit.
|
|
# WHY:
|
|
# Even a CTRL-C will call this and we will not leave temp files.
|
|
# Also, if a configuration file breaks, we will detect this too.
|
|
FIREHOL_CLEAN_TMP=1
|
|
FIREHOL_ACTIVATED_SUCCESSFULLY=0
|
|
|
|
syslog() {
|
|
local p="$1"; shift
|
|
|
|
${LOGGER_CMD} -p ${FIREHOL_SYSLOG_FACILITY}.$p -t "FireHOL[$$]" -- "${@}"
|
|
return 0
|
|
}
|
|
|
|
declare -a FIREHOL_PROGRESS_MESSAGES=()
|
|
progress() {
|
|
printf >&2 "${COLOR_GREEN}FireHOL:${COLOR_RESET} ${*}... "
|
|
FIREHOL_PROGRESS_MESSAGES=("${*}" "${FIREHOL_PROGRESS_MESSAGES[@]}")
|
|
syslog info "${*} started"
|
|
}
|
|
|
|
success() {
|
|
if [ ! -z "${1}" ]
|
|
then
|
|
echo >&2 -e "${COLOR_RESET}${COLOR_BGGREEN}${COLOR_BLACK}${COLOR_BOLD} OK ${COLOR_RESET} (${*})"
|
|
syslog info "${FIREHOL_PROGRESS_MESSAGES[0]} succeeded with message: ${*}"
|
|
else
|
|
echo >&2 -e "${COLOR_RESET}${COLOR_BGGREEN}${COLOR_BLACK}${COLOR_BOLD} OK ${COLOR_RESET}"
|
|
syslog info "${FIREHOL_PROGRESS_MESSAGES[0]} succeeded"
|
|
fi
|
|
unset FIREHOL_PROGRESS_MESSAGES[0]
|
|
FIREHOL_PROGRESS_MESSAGES=("${FIREHOL_PROGRESS_MESSAGES[@]}")
|
|
}
|
|
|
|
failure() {
|
|
if [ ! -z "${1}" ]
|
|
then
|
|
echo >&2 -e "${COLOR_RESET}${COLOR_BGRED}${COLOR_WHITE}${COLOR_BOLD}${COLOR_BLINK} FAILED ${COLOR_RESET} (${*})"
|
|
syslog err "${FIREHOL_PROGRESS_MESSAGES[0]} failed with message: ${*}"
|
|
else
|
|
echo >&2 -e "${COLOR_RESET}${COLOR_BGRED}${COLOR_WHITE}${COLOR_BOLD}${COLOR_BLINK} FAILED ${COLOR_RESET}"
|
|
syslog err "${FIREHOL_PROGRESS_MESSAGES[0]} failed"
|
|
fi
|
|
unset FIREHOL_PROGRESS_MESSAGES[0]
|
|
FIREHOL_PROGRESS_MESSAGES=("${FIREHOL_PROGRESS_MESSAGES[@]}")
|
|
}
|
|
|
|
FIREHOL_IPSET_TMP_COUNTER=0
|
|
declare -A FIREHOL_IPSET_TMP_SETS=()
|
|
|
|
ipset_done_all_tmp_sets() {
|
|
# empty temp variables to prevent cleanup from running at exit
|
|
FIREHOL_IPSET_TMP_SETS=()
|
|
}
|
|
|
|
ipset_remove_all_tmp_sets() {
|
|
if [ ${ENABLE_IPSET} -eq 1 ]
|
|
then
|
|
local x=
|
|
for x in ${!FIREHOL_IPSET_TMP_SETS[@]}
|
|
do
|
|
${IPSET_CMD} ${IPSET_DESTROY_OPTION} "${x}" >/dev/null 2>&1
|
|
done
|
|
ipset_done_all_tmp_sets
|
|
fi
|
|
}
|
|
|
|
firehol_exit() {
|
|
local restored="NO"
|
|
if [ $RUNNING_ON_TERMINAL -eq 1 ]
|
|
then
|
|
$STTY_CMD sane
|
|
fi
|
|
if [ \( -f "${FIREHOL_SAVED}" -o -f "${FIREHOL_SAVED6}" \) -a "${FIREHOL_MODE}" = "START" ]
|
|
then
|
|
echo >&2
|
|
progress "Restoring old firewall"
|
|
local status4=0
|
|
local status6=0
|
|
if [ $ENABLE_IPV4 -eq 1 ]
|
|
then
|
|
${IPTABLES_RESTORE_CMD} <"${FIREHOL_SAVED}"
|
|
status4=$?
|
|
fi
|
|
if [ $ENABLE_IPV6 -eq 1 ]
|
|
then
|
|
${IP6TABLES_RESTORE_CMD} <"${FIREHOL_SAVED6}"
|
|
status6=$?
|
|
fi
|
|
if [ $status4 -eq 0 -a $status6 -eq 0 ]
|
|
then
|
|
restored="OK"
|
|
success # "Restoring old firewall"
|
|
else
|
|
restored="FAILED"
|
|
failure # "Restoring old firewall"
|
|
fi
|
|
fi
|
|
|
|
# remove the temporary directory created for this session
|
|
if [ ${FIREHOL_ACTIVATED_SUCCESSFULLY} -eq 0 -a ${FIREHOL_CLEAN_TMP} -eq 0 ]
|
|
then
|
|
echo >&2 "FireHOL: temporary files left in ${FIREHOL_DIR}"
|
|
else
|
|
test -d "${FIREHOL_DIR}" && ${RM_CMD} -rf "${FIREHOL_DIR}"
|
|
fi
|
|
|
|
# syslog
|
|
local result=
|
|
local notify=0
|
|
case "${FIREHOL_MODE}" in
|
|
START) if [ ${FIREHOL_ACTIVATED_SUCCESSFULLY} -eq 0 ]
|
|
then
|
|
syslog emerg "FAILED to activate the firewall from ${FIREHOL_CONFIG}. Last good firewall restoration: ${restored}."
|
|
result="FAILED"
|
|
else
|
|
# save the marks information for the other tools
|
|
declare -p MARKS_BITS MARKS_MASKS MARKS_MAX MARKS_SHIFT MARKS_STATEFUL MARKS_SAVERESTORE MARKS_SAVERESTORE_STATEFUL_MASK MARKS_SAVERESTORE_STATELESS_MASK >"${FIREHOL_SPOOL_DIR}/marks.conf"
|
|
|
|
syslog info "Successfully activated new firewall from ${FIREHOL_CONFIG}."
|
|
result="OK"
|
|
fi
|
|
notify=1
|
|
;;
|
|
|
|
STOP) syslog emerg "Firewall has been stopped. Policy is ACCEPT EVERYTHING!"
|
|
notify=1
|
|
;;
|
|
|
|
PANIC) syslog emerg "PANIC! Machine has been locked. Policy is DROP EVERYTHING!"
|
|
notify=1
|
|
;;
|
|
|
|
*) # do nothing for the rest
|
|
notify=0
|
|
;;
|
|
esac
|
|
|
|
# do we have to run a program?
|
|
if [ ${notify} -eq 1 ]
|
|
then
|
|
if [ ! -z "${FIREHOL_NOTIFICATION_PROGRAM}" -a -x "${FIREHOL_NOTIFICATION_PROGRAM}" ]
|
|
then
|
|
# we just fork it, so that it will not depend on terminal conditions
|
|
"${FIREHOL_NOTIFICATION_PROGRAM}" "${FIREHOL_CONFIG}" "${result}" "${restored}" "${work_error}" "${work_runtime_error}" >/dev/null 2>&1 </dev/null &
|
|
fi
|
|
fi
|
|
|
|
# remove any temporary ipsets that may have been left behind
|
|
ipset_remove_all_tmp_sets
|
|
|
|
enable trap
|
|
enable exit
|
|
trap exit EXIT
|
|
if [ ${FIREHOL_ACTIVATED_SUCCESSFULLY} -eq 0 ]
|
|
then
|
|
exit 1
|
|
fi
|
|
|
|
exit 0
|
|
}
|
|
|
|
# Run our exit even if we don't call exit.
|
|
trap firehol_exit EXIT
|
|
trap firehol_exit SIGHUP
|
|
trap firehol_exit INT
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# LIBRARY OF COMMON FUNCTIONS
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
# file management for BASH
|
|
|
|
file() {
|
|
local cmd="$1"
|
|
shift
|
|
|
|
case "${cmd}" in
|
|
open)
|
|
local fd="$1" filename="$2" mode="$3"
|
|
|
|
case "$mode" in
|
|
r) # read
|
|
eval "exec $fd<\"$filename\""
|
|
return $?
|
|
;;
|
|
|
|
rw) # read-write
|
|
eval "exec $fd<>\"$filename\""
|
|
return $?
|
|
;;
|
|
|
|
w) # write
|
|
eval "exec $fd>\"$filename\""
|
|
return $?
|
|
;;
|
|
|
|
*)
|
|
echo >&2 "${FUNCNAME}(): unknown mode '$mode'."
|
|
return 1
|
|
;;
|
|
esac
|
|
;;
|
|
|
|
close)
|
|
local fd="$1"
|
|
eval "exec $fd>&-"
|
|
return $?
|
|
;;
|
|
|
|
dup2)
|
|
local fd1="$1" fd2="$2"
|
|
eval "exec $fd2>&$fd1"
|
|
return $?
|
|
;;
|
|
|
|
*)
|
|
echo >&2 "${FUNCNAME}: unknown command '${cmd}'"
|
|
return 1
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
# Given a mark-type and a list of marks, this function
|
|
# calculates the bitmasked equivalent values
|
|
mark_value() {
|
|
local x= name="${1}"; shift
|
|
|
|
if [ -z "${name}" ]
|
|
then
|
|
error "Cannot find the value of mark with name '${name}'."
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "${1}" ]
|
|
then
|
|
error "Empty mark value given for mark ${name}."
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "${MARKS_MASKS[$name]}" ]
|
|
then
|
|
error "Mark $name does not exist."
|
|
return 1
|
|
fi
|
|
|
|
for x in ${@//,/ }
|
|
do
|
|
x=$[ x + 1 - 1 ]
|
|
if [ $x -gt ${MARKS_MAX[$name]} -o $x -lt 0 ]
|
|
then
|
|
error "Cannot get mark $name of value $x. Mark $name is configured to get values from 0 to ${MARKS_MAX[$name]}. Change firehol-defaults.conf to add more."
|
|
return 1
|
|
fi
|
|
|
|
printf "0x%08x/${MARKS_MASKS[$name]}\n" "$[ x << ${MARKS_SHIFT[$name]} ]"
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
# Find in the BASH execution stack, the line and the source file that has called us.
|
|
# Before first use the variable PROGRAM_FILE should be set to the file to be excluded.
|
|
# It also sets the variable LAST_CONFIG_LINE on each run.
|
|
declare -A PROGRAM_CONFIG_FILES=()
|
|
config_line() {
|
|
if [ ! -z "${FORCE_CONFIG_LINEID}" ]
|
|
then
|
|
LAST_CONFIG_LINE="${FORCE_CONFIG_LINEID}"
|
|
else
|
|
# find the config line in the BASH stack
|
|
# start from 2
|
|
# 0 is this line
|
|
# 1 is the caller - our line for sure
|
|
# 2 is the caller's caller - possibly a config file line
|
|
local i= all=${#BASH_SOURCE} cfg=
|
|
for (( i = 2; i < $all; i++ ))
|
|
do
|
|
[ ! "${BASH_SOURCE[$i]}" = "${PROGRAM_FILE}" ] && break
|
|
done
|
|
cfg="${BASH_SOURCE[$i]}"
|
|
if [ ! "${cfg}" = "${PROGRAM_CONFIG}" -a -z "${PROGRAM_CONFIG_FILES[$cfg]}" ]
|
|
then
|
|
syslog info "Processing configuration file '${cfg}'..."
|
|
PROGRAM_CONFIG_FILES[$cfg]=1
|
|
fi
|
|
LAST_CONFIG_LINE="${BASH_LINENO[$[i-1]]}@${cfg}: ${FUNCNAME[$[i-1]]}:"
|
|
fi
|
|
test ! "z$1" = "z-ne" && echo "${LAST_CONFIG_LINE}"
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Create the directories we need.
|
|
|
|
if [ ! -d "${FIREHOL_CONFIG_DIR}" ]
|
|
then
|
|
${MKDIR_CMD} "${FIREHOL_CONFIG_DIR}" || exit 1
|
|
${CHOWN_CMD} root:root "${FIREHOL_CONFIG_DIR}" || exit 1
|
|
${CHMOD_CMD} 700 "${FIREHOL_CONFIG_DIR}" || exit 1
|
|
|
|
if [ -f /etc/firehol.conf ]
|
|
then
|
|
${MV_CMD} /etc/firehol.conf "${FIREHOL_CONFIG}" || exit 1
|
|
|
|
echo >&2
|
|
echo >&2
|
|
echo >&2 "NOTICE: Your config file /etc/firehol.conf has been moved to ${FIREHOL_CONFIG}"
|
|
echo >&2
|
|
$SLEEP_CMD 5
|
|
fi
|
|
fi
|
|
|
|
# Externally defined services can be placed in "${FIREHOL_SERVICES_DIR}"
|
|
if [ ! -d "${FIREHOL_SERVICES_DIR}" ]
|
|
then
|
|
${MKDIR_CMD} -p "${FIREHOL_SERVICES_DIR}"
|
|
if [ $? -ne 0 ]
|
|
then
|
|
echo >&2
|
|
echo >&2
|
|
echo >&2 "FireHOL needs to create the directory '${FIREHOL_SERVICES_DIR}', but it cannot."
|
|
echo >&2 "Possibly you have a file with this name, or something else is happening."
|
|
echo >&2 "Please solve this issue and retry".
|
|
echo >&2
|
|
exit 1
|
|
fi
|
|
${CHOWN_CMD} root:root "${FIREHOL_SERVICES_DIR}"
|
|
${CHMOD_CMD} 700 "${FIREHOL_SERVICES_DIR}"
|
|
fi
|
|
|
|
#${MKDIR_CMD} "${FIREHOL_CHAINS_DIR}" || exit 1
|
|
${MKDIR_CMD} "${FIREHOL_DIR}/fast" || exit 1
|
|
${MKDIR_CMD} "${FIREHOL_DIR}/fast/tables" || exit 1
|
|
${MKDIR_CMD} "${FIREHOL_DIR}/fast/table6s" || exit 1
|
|
|
|
# prepare the file that will hold all modules to be loaded.
|
|
# this is needed only when we are going to save the firewall
|
|
# with iptables-save.
|
|
file open 20 "${FIREHOL_DIR}/firewall_restore_commands.sh" w || exit 1
|
|
$CAT_CMD >&20 <<EOFMTL
|
|
#!/bin/sh
|
|
# Generated by FireHOL to execute additional actions
|
|
# to restore the generated firewall.
|
|
#
|
|
|
|
# a function to help us save a value to a file
|
|
postprocess_echo_to() { echo "\${1}" >"\${2}"; }
|
|
postprocess_wait_netfilter() {
|
|
maxwait=60
|
|
waiting=0
|
|
while [ ! -f /proc/sys/net/netfilter/nf_conntrack_max -a ! -f /proc/sys/net/netfilter/nf_conntrack_tcp_max_retrans -a \${waiting} -le \${maxwait} ]
|
|
do
|
|
echo >&2 "netfilter is not ready; waiting \${waiting} of \${maxwait}"
|
|
${SLEEP_CMD} 1
|
|
waiting=\$(( waiting + 1 ))
|
|
done
|
|
[ \${waiting} -gt \${maxwait} ] && return 1
|
|
return 0
|
|
}
|
|
|
|
EOFMTL
|
|
|
|
# source it to have the functions available here too
|
|
# we need these for sourcing our output file
|
|
source "${FIREHOL_DIR}/firewall_restore_commands.sh"
|
|
|
|
# prepare the file that will hold the generated iptables commands
|
|
# when FAST_ACTIVATION is zero
|
|
file open 21 "${FIREHOL_OUTPUT}" w || exit 1
|
|
|
|
# prepare the file that will host postprocess2 commands
|
|
file open 22 "${FIREHOL_OUTPUT}.postprocess2" w || exit 1
|
|
|
|
# Make sure we have a directory for our data.
|
|
if [ ! -d "${FIREHOL_SPOOL_DIR}" ]
|
|
then
|
|
${MKDIR_CMD} "${FIREHOL_SPOOL_DIR}" || exit 1
|
|
${CHOWN_CMD} root:root "${FIREHOL_SPOOL_DIR}" || exit 1
|
|
${CHMOD_CMD} 700 "${FIREHOL_SPOOL_DIR}" || exit 1
|
|
fi
|
|
|
|
# Generate firehol-defaults.conf file
|
|
if [ ! -f "${FIREHOL_CONFIG_DIR}/firehol-defaults.conf" ]
|
|
then
|
|
$SED_CMD -n -e "s:\$FIREHOL_CONFIG_DIR:$FIREHOL_CONFIG_DIR:" \
|
|
-e '/^# --- BEGIN OF FIREHOL DEFAULTS ---/,/^# --- END OF FIREHOL DEFAULTS ---/p' \
|
|
"${PROGRAM_FILE}" > "${FIREHOL_CONFIG_DIR}/firehol-defaults.conf" || exit 1
|
|
${CHOWN_CMD} root:root "${FIREHOL_CONFIG_DIR}/firehol-defaults.conf" || exit 1
|
|
${CHMOD_CMD} 644 "${FIREHOL_CONFIG_DIR}/firehol-defaults.conf" || exit 1
|
|
fi
|
|
|
|
load_ips() {
|
|
local v="${1}" # the variable
|
|
local f="${2}" # the old file-name
|
|
local d="${3}" # the default value
|
|
local dt="${4}" # days old
|
|
local m="${5}" # additional info for file generation
|
|
local c="${6}" # if set, complain if file is missing
|
|
|
|
# We load from a file with the variable name if found but will use
|
|
# the old file name for compatibility
|
|
if [ "${f}" != ${v} \
|
|
-a -f "${FIREHOL_CONFIG_DIR}/${f}" \
|
|
-a -f "${FIREHOL_CONFIG_DIR}/${v}" ]
|
|
then
|
|
echo >&2 "WARNING "
|
|
echo >&2 "Found ${f} and ${v} in '${FIREHOL_CONFIG_DIR}'"
|
|
echo >&2 "Using ${v}"
|
|
f=${v}
|
|
elif [ -f "${FIREHOL_CONFIG_DIR}/${v}" ]
|
|
then
|
|
f=${v}
|
|
else
|
|
: # Using the 'old' name
|
|
fi
|
|
|
|
if [ ! -f "${FIREHOL_CONFIG_DIR}/${f}" ]
|
|
then
|
|
if [ ! -z "${c}" ]
|
|
then
|
|
echo >&2
|
|
echo >&2
|
|
echo >&2 "WARNING "
|
|
echo >&2 "Cannot find file '${FIREHOL_CONFIG_DIR}/${v}'."
|
|
echo >&2 "Using internal default values for variable '${v}' and all inherited ones."
|
|
echo >&2
|
|
|
|
if [ ! -z "${m}" ]
|
|
then
|
|
echo >&2 "${m}"
|
|
echo >&2
|
|
fi
|
|
fi
|
|
|
|
eval "export ${v}=\"${d}\""
|
|
return 0
|
|
fi
|
|
|
|
if [ ${dt} -gt 0 ]
|
|
then
|
|
local t=`${FIND_CMD} "${FIREHOL_CONFIG_DIR}/${f}" -mtime +${dt}`
|
|
if [ ! -z "${t}" ]
|
|
then
|
|
echo >&2
|
|
echo >&2
|
|
echo >&2 "WARNING"
|
|
echo >&2 "File '${FIREHOL_CONFIG_DIR}/${f}' is more than ${dt} days old."
|
|
echo >&2 "You should update it to ensure proper operation of your firewall."
|
|
echo >&2
|
|
|
|
if [ ! -z "${m}" ]
|
|
then
|
|
echo >&2 "${m}"
|
|
echo >&2
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
local t=`${CAT_CMD} "${FIREHOL_CONFIG_DIR}/${f}" | ${EGREP_CMD} "^ *[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+ *$"`
|
|
local t2=
|
|
local i=0
|
|
for x in ${t}
|
|
do
|
|
i=$[i + 1]
|
|
t2="${t2} ${x}"
|
|
done
|
|
|
|
local t6=`${CAT_CMD} "${FIREHOL_CONFIG_DIR}/${f}" | ${EGREP_CMD} "^ *((([0-9A-Fa-f]{1,4}:){7}(([0-9A-Fa-f]{1,4})|:))|(([0-9A-Fa-f]{1,4}:){6}(:|((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})|(:[0-9A-Fa-f]{1,4})))|(([0-9A-Fa-f]{1,4}:){5}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){4}(:[0-9A-Fa-f]{1,4}){0,1}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){3}(:[0-9A-Fa-f]{1,4}){0,2}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){0,3}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:)(:[0-9A-Fa-f]{1,4}){0,4}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(:(:[0-9A-Fa-f]{1,4}){0,5}((:((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(((25[0-5]|2[0-4]\d|[01]?\d{1,2})(\.(25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})))(%.+)?/[0-9]+ *$"`
|
|
for x in ${t6}
|
|
do
|
|
i=$[i + 1]
|
|
t2="${t2} ${x}"
|
|
done
|
|
|
|
if [ ${i} -eq 0 -o -z "${t2}" ]
|
|
then
|
|
echo >&2
|
|
echo >&2
|
|
echo >&2 "WARNING "
|
|
echo >&2 "The file '${FIREHOL_CONFIG_DIR}/${f}' contains zero IP definitions."
|
|
echo >&2 "Using internal default values for variable '${v}' and all inherited ones."
|
|
echo >&2
|
|
|
|
if [ ! -z "${m}" ]
|
|
then
|
|
echo >&2 "${m}"
|
|
echo >&2
|
|
fi
|
|
|
|
eval "export ${v}=\"${d}\""
|
|
return 0
|
|
fi
|
|
|
|
eval "export ${v}=\"${t2}\""
|
|
return 0
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# IP definitions
|
|
|
|
# IANA Reserved IPv4 address space.
|
|
load_ips RESERVED_IPV4 RESERVED_IPS "${RESERVED_IPV4}" 0
|
|
load_ips RESERVED_IPV6 RESERVED_IPV6 "${RESERVED_IPV6}" 0
|
|
|
|
# Make the original name a context-dependent function
|
|
RESERVED_IPS="reserved_ips()"
|
|
reserved_ips() {
|
|
if running_both; then
|
|
error "Cannot be called in 'both' mode"
|
|
return 1
|
|
fi
|
|
if running_ipv6; then
|
|
echo "${RESERVED_IPV6}"
|
|
else
|
|
echo "${RESERVED_IPV4}"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# private IP address space
|
|
load_ips PRIVATE_IPV4 PRIVATE_IPS "${PRIVATE_IPV4}" 0
|
|
load_ips PRIVATE_IPV6 PRIVATE_IPV6 "${PRIVATE_IPV6}" 0
|
|
|
|
PRIVATE_IPS="private_ips()"
|
|
private_ips() {
|
|
if running_both; then
|
|
error "Cannot be called in 'both' mode"
|
|
return 1
|
|
fi
|
|
if running_ipv6; then
|
|
echo "${PRIVATE_IPV6}"
|
|
else
|
|
echo "${PRIVATE_IPV4}"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# The multicast address space
|
|
load_ips MULTICAST_IPV4 MULTICAST_IPS "${MULTICAST_IPV4}" 0
|
|
load_ips MULTICAST_IPV6 MULTICAST_IPV6 "${MULTICAST_IPV6}" 0
|
|
|
|
MULTICAST_IPS="multicast_ips()"
|
|
multicast_ips() {
|
|
if running_both; then
|
|
error "Cannot be called in 'both' mode"
|
|
return 1
|
|
fi
|
|
if running_ipv6; then
|
|
echo "${MULTICAST_IPV6}"
|
|
else
|
|
echo "${MULTICAST_IPV4}"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# A shortcut to have all the Internet unroutable addresses in one
|
|
# variable
|
|
UNROUTABLE_IPV4="${RESERVED_IPV4} ${PRIVATE_IPV4}"
|
|
load_ips UNROUTABLE_IPV4 UNROUTABLE_IPS "${UNROUTABLE_IPV4}" 0
|
|
|
|
UNROUTABLE_IPV6="${RESERVED_IPV6} ${PRIVATE_IPV6}"
|
|
load_ips UNROUTABLE_IPV6 UNROUTABLE_IPV6 "${UNROUTABLE_IPV6}" 0
|
|
|
|
UNROUTABLE_IPS="unroutable_ips()"
|
|
unroutable_ips() {
|
|
if running_both; then
|
|
error "Cannot be called in 'both' mode"
|
|
return 1
|
|
fi
|
|
if running_ipv6; then
|
|
echo "${UNROUTABLE_IPV6}"
|
|
else
|
|
echo "${UNROUTABLE_IPV4}"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Get the default client ports from the kernel configuration.
|
|
# This is formed to a range of ports to be used for all "default"
|
|
# client ports when the client specified is the localhost.
|
|
#
|
|
# According to http://tldp.org/HOWTO/Linux+IPv6-HOWTO/proc-sys-net-ipv4..html
|
|
# the ipv4 values are also used for ipv6, so no needed change here
|
|
LOCAL_CLIENT_PORTS_LOW=`${SYSCTL_CMD} net.ipv4.ip_local_port_range | ${CUT_CMD} -d '=' -f 2 | ${CUT_CMD} -f 1`
|
|
LOCAL_CLIENT_PORTS_HIGH=`${SYSCTL_CMD} net.ipv4.ip_local_port_range | ${CUT_CMD} -d '=' -f 2 | ${CUT_CMD} -f 2`
|
|
LOCAL_CLIENT_PORTS="${LOCAL_CLIENT_PORTS_LOW}:${LOCAL_CLIENT_PORTS_HIGH}"
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# This is our version number. It is increased when the configuration
|
|
# file commands and arguments change their meaning and usage, so that
|
|
# the user will have to review it more precisely.
|
|
FIREHOL_VERSION=6
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# The initial line number of the configuration file.
|
|
FORCE_CONFIG_LINEID="INIT"
|
|
LAST_CONFIG_LINE="INIT"
|
|
|
|
# Variable kernel module requirements.
|
|
# Suggested by Fco.Felix Belmonte <ffelix@gescosoft.com>
|
|
# Note that each of the complex services
|
|
# may add to this variable the kernel modules it requires.
|
|
declare -A FIREHOL_KERNEL_MODULES=()
|
|
|
|
|
|
#
|
|
# In the configuration file you can write:
|
|
#
|
|
# require_kernel_module <module_name>
|
|
#
|
|
# to have FireHOL require a specific module for the configurarion.
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Various Defaults
|
|
|
|
# Valid modes:
|
|
# START, DEBUG, EXPLAIN, WIZARD, STOP, PANIC
|
|
FIREHOL_MODE="NONE"
|
|
|
|
# If set to 1, the firewall will be saved for normal iptables processing.
|
|
# Valid only for FIREHOL_MODE="START"
|
|
FIREHOL_SAVE=0
|
|
|
|
# If set to 1, the firewall will be restored if you don't commit it.
|
|
# Valid only for FIREHOL_MODE="START"
|
|
FIREHOL_TRY=0
|
|
|
|
# If set to 1, firehol will output the commands of the configuration file
|
|
# with variables expanded.
|
|
FIREHOL_CONF_SHOW=0
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Keep information about the current namespace: ipv4, ipv6 or both
|
|
declare -a FIREHOL_NS_STACK=()
|
|
FIREHOL_NS_CURR=
|
|
FIREHOL_DEFAULT_NAMESPACE=
|
|
|
|
init_namespace() {
|
|
[ ! -z "${FIREHOL_NS_CURR}" ] && return 0
|
|
|
|
if [ $ENABLE_IPV4 -eq 1 -a $ENABLE_IPV6 -eq 1 ]
|
|
then
|
|
FIREHOL_DEFAULT_NAMESPACE="both"
|
|
|
|
elif [ $ENABLE_IPV4 -eq 1 ]
|
|
then
|
|
FIREHOL_DEFAULT_NAMESPACE="ipv4"
|
|
else
|
|
FIREHOL_DEFAULT_NAMESPACE="ipv6"
|
|
fi
|
|
|
|
FIREHOL_NS_STACK=(${FIREHOL_DEFAULT_NAMESPACE})
|
|
FIREHOL_NS_CURR=${FIREHOL_DEFAULT_NAMESPACE}
|
|
}
|
|
|
|
init_namespace
|
|
|
|
push_namespace() {
|
|
if [ "$1" != "ipv4" -a "$1" != "ipv6" -a "$1" != "both" ]
|
|
then
|
|
error "Bad namespace: $1 (must be ipv4/ipv6/both)"
|
|
return 1
|
|
fi
|
|
if [ "${FIREHOL_NS_CURR}" != "both" -a "$1" != "${FIREHOL_NS_CURR}" ]
|
|
then
|
|
error "Cannot use namespace $1 within ${FIREHOL_NS_CURR}"
|
|
return 1
|
|
fi
|
|
FIREHOL_NS_STACK=("$1" "${FIREHOL_NS_STACK[@]}")
|
|
FIREHOL_NS_CURR="$1"
|
|
return 0
|
|
}
|
|
pop_namespace() {
|
|
FIREHOL_NS_STACK=(${FIREHOL_NS_STACK[@]:1})
|
|
FIREHOL_NS_CURR=${FIREHOL_NS_STACK[0]-${FIREHOL_DEFAULT_NAMESPACE}}
|
|
return 0
|
|
}
|
|
|
|
running_ipv4() {
|
|
if [ "${FIREHOL_NS_CURR}" = "ipv4" -o "${FIREHOL_NS_CURR}" = "both" ]
|
|
then
|
|
return 0;
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
running_ipv6() {
|
|
if [ "${FIREHOL_NS_CURR}" = "ipv6" -o "${FIREHOL_NS_CURR}" = "both" ]
|
|
then
|
|
return 0;
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
running_both() {
|
|
if [ "${FIREHOL_NS_CURR}" = "both" ]
|
|
then
|
|
return 0;
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
ipv4() { force_namespace ipv4 "${@}"; }
|
|
ipv6() { force_namespace ipv6 "${@}"; }
|
|
both() { force_namespace both "${@}"; }
|
|
force_namespace() {
|
|
test -z "${FIREHOL_DEFAULT_NAMESPACE}" && init_namespace
|
|
|
|
local ipv="${1}" command="${2}" ret=0
|
|
shift 2
|
|
|
|
case "${command}" in
|
|
# these commands push/pop the namespace by themselves
|
|
interface|interface4|interface6|interface46|router|router4|router6|router46|group|group4|group6|group46)
|
|
${command} -ns ${ipv} "${@}"
|
|
ret=$?
|
|
;;
|
|
|
|
# all the others complete in just one step
|
|
# so, we push/pop for them
|
|
*)
|
|
if ! push_namespace ${ipv}; then return 1; fi
|
|
$command "${@}"
|
|
ret=$?
|
|
pop_namespace
|
|
;;
|
|
esac
|
|
|
|
return $ret
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Keep information about the current primary command
|
|
# Primary commands are: interface, router
|
|
|
|
work_counter4=0
|
|
work_counter6=0
|
|
work_cmd=
|
|
work_realcmd=("(unset)")
|
|
work_name=
|
|
work_inface=
|
|
work_outface=
|
|
work_policy=
|
|
work_error=0
|
|
work_function="Initializing"
|
|
|
|
get_next_work_counter() {
|
|
local var="$1"
|
|
if running_both
|
|
then
|
|
if [ $work_counter4 -gt $work_counter6 ]
|
|
then
|
|
work_counter4=$[work_counter4 + 1]
|
|
work_counter6=$[work_counter4]
|
|
else
|
|
work_counter6=$[work_counter6 + 1]
|
|
work_counter4=$[work_counter6]
|
|
fi
|
|
eval ${var}=${work_counter4}
|
|
elif running_ipv6
|
|
then
|
|
work_counter6=$[work_counter6 + 1]
|
|
eval ${var}=${work_counter6}
|
|
else
|
|
work_counter4=$[work_counter4 + 1]
|
|
eval ${var}=${work_counter4}
|
|
fi
|
|
}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Keep status information
|
|
|
|
# 0 = no errors, >0 = there were errors in the script
|
|
work_runtime_error=0
|
|
|
|
# This function is used for generating dynamic chains when needed for
|
|
# combined negative statements (AND) implied by the "not" parameter
|
|
# to many FireHOL directives.
|
|
# What FireHOL is doing to accomplish this, is to produce dynamically
|
|
# a linked list of iptables chains with just one condition each, making
|
|
# the packets to traverse from chain to chain when matched, to reach
|
|
# their final destination.
|
|
dynamic_counter4=0
|
|
dynamic_counter6=0
|
|
|
|
get_next_dynamic_counter() {
|
|
local var="$1"
|
|
if running_both
|
|
then
|
|
if [ $dynamic_counter4 -gt $dynamic_counter6 ]
|
|
then
|
|
dynamic_counter4=$[dynamic_counter4 + 1]
|
|
dynamic_counter6=$[dynamic_counter4]
|
|
else
|
|
dynamic_counter6=$[dynamic_counter6 + 1]
|
|
dynamic_counter4=$[dynamic_counter6]
|
|
fi
|
|
eval ${var}=${dynamic_counter4}
|
|
elif running_ipv6
|
|
then
|
|
dynamic_counter6=$[dynamic_counter6 + 1]
|
|
eval ${var}=${dynamic_counter6}
|
|
else
|
|
dynamic_counter4=$[dynamic_counter4 + 1]
|
|
eval ${var}=${dynamic_counter4}
|
|
fi
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# COMPLEX SERVICES DEFINITIONS
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
# The following are definitions for complex services.
|
|
# We define as "complex" the services that are implemented using multiple sockets.
|
|
|
|
# Each function below is organized in three parts:
|
|
# 1) A Header, common to each and every function
|
|
# 2) The rules required for the INPUT of the server
|
|
# 3) The rules required for the OUTPUT of the server
|
|
#
|
|
# The Header part, together with the "reverse" keyword can reverse the rules so
|
|
# that if we are implementing a client the INPUT will become OUTPUT and vice versa.
|
|
#
|
|
# In most the cases the input and output rules are the same with the following
|
|
# differences:
|
|
#
|
|
# a) The output rules begin with the "reverse" keyword, which reverses:
|
|
# inface/outface, src/dst, sport/dport
|
|
# b) The output rules use ${out}_${mychain} instead of ${in}_${mychain}
|
|
# c) The state rules match the client operation, not the server.
|
|
|
|
# --- ICMP (v4/v6) helper functions --------------------------------------------
|
|
|
|
add_icmp_rule_pair() {
|
|
local in=in out=out \
|
|
mychain="${1}" \
|
|
type="${2}" \
|
|
request="${3}" \
|
|
response="${4}"
|
|
shift 4
|
|
|
|
if [ "${type}" = "client" ]
|
|
then
|
|
in=out
|
|
out=in
|
|
fi
|
|
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto icmp custom "--icmp-type $request" state NEW,ESTABLISHED || return 1
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto icmp custom "--icmp-type $response" state ESTABLISHED || return 1
|
|
|
|
return 0
|
|
}
|
|
|
|
add_icmpv6_rule_pair() {
|
|
local in=in out=out \
|
|
mychain="${1}" \
|
|
type="${2}" \
|
|
request="${3}" \
|
|
response="${4}"
|
|
shift 4
|
|
|
|
if [ "${type}" = "client" ]
|
|
then
|
|
in=out
|
|
out=in
|
|
fi
|
|
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto icmpv6 custom "--icmpv6-type $request" state NEW,ESTABLISHED || return 1
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto icmpv6 custom "--icmpv6-type $response" state ESTABLISHED || return 1
|
|
|
|
return 0
|
|
}
|
|
|
|
add_icmpv6_rule_pair_stateless() {
|
|
local in=in out=out icmptype="" \
|
|
mychain="${1}" \
|
|
type="${2}" \
|
|
icmpv6in="${3}" \
|
|
icmpv6out="${4}"
|
|
shift 4
|
|
|
|
if [ "${type}" = "client" ]
|
|
then
|
|
in=out
|
|
out=in
|
|
fi
|
|
|
|
for icmptype in $icmpv6in
|
|
do
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto icmpv6 custom "--icmpv6-type $icmptype" || return 1
|
|
done
|
|
|
|
for icmptype in $icmpv6out
|
|
do
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto icmpv6 custom "--icmpv6-type $icmptype" || return 1
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
# --- XBOX ---------------------------------------------------------------------
|
|
|
|
# Contributed by andrex@alumni.utexas.net
|
|
# Following is the (complex) service definition function for xbox, the Xbox live
|
|
# service. With this definition our Xbox connects and plays from behind a NAT
|
|
# firewall with no trouble. Andrew.
|
|
|
|
rules_xbox() {
|
|
local in=in out=out \
|
|
client_ports="${DEFAULT_CLIENT_PORTS}" \
|
|
mychain="${1}" \
|
|
type="${2}"
|
|
shift 2
|
|
|
|
if [ "${type}" = "client" ]
|
|
then
|
|
in=out
|
|
out=in
|
|
fi
|
|
|
|
if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
|
|
then
|
|
client_ports="${LOCAL_CLIENT_PORTS}"
|
|
fi
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
set_work_function "Rules for Xbox live"
|
|
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto udp dport "88 3074" sport "${client_ports}" state NEW,ESTABLISHED || return 1
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto udp dport "88 3074" sport "${client_ports}" state ESTABLISHED || return 1
|
|
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto tcp dport 3074 sport "${client_ports}" state NEW,ESTABLISHED || return 1
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto tcp dport 3074 sport "${client_ports}" state ESTABLISHED || return 1
|
|
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto udp sport 3074 dport "${client_ports}" state NEW,ESTABLISHED || return 1
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto udp sport 3074 dport "${client_ports}" state ESTABLISHED || return 1
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# --- DHCP --------------------------------------------------------------------
|
|
|
|
rules_dhcp() {
|
|
local in=in out=out \
|
|
client_ports="${DEFAULT_CLIENT_PORTS}" \
|
|
mychain="${1}" \
|
|
type="${2}"
|
|
shift 2
|
|
|
|
if ! push_namespace ipv4; then return 1; fi
|
|
|
|
if [ "${type}" = "client" ]
|
|
then
|
|
in=out
|
|
out=in
|
|
fi
|
|
|
|
if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
|
|
then
|
|
client_ports="${LOCAL_CLIENT_PORTS}"
|
|
fi
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
set_work_function "Rules for DHCP (${type})"
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto "udp" sport "68" dport "67" || return 1
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto "udp" sport "68" dport "67" || return 1
|
|
|
|
pop_namespace
|
|
return 0
|
|
}
|
|
|
|
rules_dhcpv6() {
|
|
local in=in out=out \
|
|
client_ports="${DEFAULT_CLIENT_PORTS}" \
|
|
mychain="${1}" \
|
|
type="${2}"
|
|
shift 2
|
|
|
|
if ! push_namespace ipv6; then return 1; fi
|
|
|
|
if [ "${type}" = "client" ]
|
|
then
|
|
in=out
|
|
out=in
|
|
fi
|
|
|
|
if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
|
|
then
|
|
client_ports="${LOCAL_CLIENT_PORTS}"
|
|
fi
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
set_work_function "Rules for DHCPv6 (${type})"
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto "udp" dport "547" || return 1
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto "udp" sport "546" || return 1
|
|
|
|
pop_namespace
|
|
return 0
|
|
}
|
|
|
|
|
|
# --- EMULE --------------------------------------------------------------------
|
|
|
|
rules_emule() {
|
|
local in=in out=out \
|
|
client_ports="${DEFAULT_CLIENT_PORTS}" \
|
|
mychain="${1}" \
|
|
type="${2}"
|
|
shift 2
|
|
|
|
if [ "${type}" = "client" ]
|
|
then
|
|
in=out
|
|
out=in
|
|
fi
|
|
|
|
if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
|
|
then
|
|
client_ports="${LOCAL_CLIENT_PORTS}"
|
|
fi
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
# allow incoming to server tcp/4662
|
|
set_work_function "Rules for EMULE/client-to-server tcp/4662 (${type})"
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto "tcp" sport any dport 4662 state NEW,ESTABLISHED || return 1
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto "tcp" sport any dport 4662 state ESTABLISHED || return 1
|
|
|
|
# allow outgoing to client tcp/4662
|
|
set_work_function "Rules for EMULE/server-to-client tcp/4662 (${type})"
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto "tcp" dport any sport 4662 state NEW,ESTABLISHED || return 1
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto "tcp" dport any sport 4662 state ESTABLISHED || return 1
|
|
|
|
# allow incoming to server udp/4672
|
|
set_work_function "Rules for EMULE/client-to-server udp/4672 (${type})"
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto "udp" sport any dport 4672 state NEW,ESTABLISHED || return 1
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto "udp" sport any dport 4672 state ESTABLISHED || return 1
|
|
|
|
# allow outgoing to client udp/4672
|
|
set_work_function "Rules for EMULE/server-to-client udp/4672 (${type})"
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto "udp" dport any sport 4672 state NEW,ESTABLISHED || return 1
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto "udp" dport any sport 4672 state ESTABLISHED || return 1
|
|
|
|
# allow incoming to server tcp/4661
|
|
set_work_function "Rules for EMULE/client-to-server tcp/4661 (${type})"
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto "tcp" sport any dport 4661 state NEW,ESTABLISHED || return 1
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto "tcp" sport any dport 4661 state ESTABLISHED || return 1
|
|
|
|
# allow incoming to server udp/4665
|
|
set_work_function "Rules for EMULE/client-to-server udp/4665 (${type})"
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto "udp" sport any dport 4665 state NEW,ESTABLISHED || return 1
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto "udp" sport any dport 4665 state ESTABLISHED || return 1
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# --- HYLAFAX ------------------------------------------------------------------
|
|
# Written by: Franscisco Javier Felix <ffelix@gescosoft.com>
|
|
|
|
rules_hylafax() {
|
|
local in=in out=out \
|
|
client_ports="${DEFAULT_CLIENT_PORTS}" \
|
|
mychain="${1}" \
|
|
type="${2}"
|
|
shift 2
|
|
|
|
if [ "${type}" = "client" ]
|
|
then
|
|
in=out
|
|
out=in
|
|
fi
|
|
|
|
if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
|
|
then
|
|
client_ports="${LOCAL_CLIENT_PORTS}"
|
|
fi
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
# allow incoming to server tcp/4559
|
|
set_work_function "Rules for HYLAFAX/client-to-server tcp/4559 (${type})"
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto "tcp" sport any dport 4559 state NEW,ESTABLISHED || return 1
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto "tcp" sport any dport 4559 state ESTABLISHED || return 1
|
|
|
|
# allow outgoing to client from server tcp/4558
|
|
set_work_function "Rules for HYLAFAX/server-to-client from server tcp/4558 (${type})"
|
|
rule ${out} action "${@}" chain "${out}_${mychain}" proto "tcp" sport 4558 dport any state NEW,ESTABLISHED || return 1
|
|
rule ${in} reverse action "${@}" chain "${in}_${mychain}" proto "tcp" sport 4558 dport any state ESTABLISHED || return 1
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# --- SAMBA --------------------------------------------------------------------
|
|
|
|
rules_samba() {
|
|
local in=in out=out \
|
|
client_ports="${DEFAULT_CLIENT_PORTS}" \
|
|
mychain="${1}" \
|
|
type="${2}"
|
|
shift 2
|
|
|
|
if [ "${type}" = "client" ]
|
|
then
|
|
in=out
|
|
out=in
|
|
fi
|
|
|
|
if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
|
|
then
|
|
client_ports="${LOCAL_CLIENT_PORTS}"
|
|
fi
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
set_work_function "Rules for SAMBA/NETBIOS-NS (${type})"
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto "udp" sport "137 ${client_ports}" dport 137 state NEW,ESTABLISHED || return 1
|
|
|
|
# NETBIOS initiates based on the broadcast address of an interface
|
|
# (request goes to broadcast address) but the server responds from
|
|
# its own IP address. This makes the server samba accept statement
|
|
# drop the server reply.
|
|
# Below is a hack, that allows a linux samba server to respond
|
|
# correctly, as it allows new outgoing connections from the well
|
|
# known netbios-ns port to the clients high ports.
|
|
# For clients and routers this hack is not applied because it
|
|
# would be a huge security hole.
|
|
if [ "${type}" = "server" -a "${work_cmd}" = "interface" ]
|
|
then
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto "udp" sport "137 ${client_ports}" dport 137 state NEW,ESTABLISHED || return 1
|
|
else
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto "udp" sport "137 ${client_ports}" dport 137 state ESTABLISHED || return 1
|
|
fi
|
|
|
|
set_work_function "Rules for SAMBA/NETBIOS-DGM (${type})"
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto "udp" sport "138 ${client_ports}" dport 138 state NEW,ESTABLISHED || return 1
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto "udp" sport "138 ${client_ports}" dport 138 state ESTABLISHED || return 1
|
|
|
|
set_work_function "Rules for SAMBA/NETBIOS-SSN (${type})"
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto "tcp" sport "${client_ports}" dport 139 state NEW,ESTABLISHED || return 1
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto "tcp" sport "${client_ports}" dport 139 state ESTABLISHED || return 1
|
|
|
|
set_work_function "Rules for SAMBA/MICROSOFT_DS (${type})"
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto "tcp" sport "${client_ports}" dport 445 state NEW,ESTABLISHED || return 1
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto "tcp" sport "${client_ports}" dport 445 state ESTABLISHED || return 1
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# --- NFS ----------------------------------------------------------------------
|
|
|
|
rules_nfs() {
|
|
local in=in out=out \
|
|
client_ports="${DEFAULT_CLIENT_PORTS}" \
|
|
mychain="${1}" \
|
|
type="${2}"
|
|
shift 2
|
|
|
|
if [ "${type}" = "client" ]
|
|
then
|
|
in=out
|
|
out=in
|
|
fi
|
|
|
|
if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
|
|
then
|
|
client_ports="${LOCAL_CLIENT_PORTS}"
|
|
fi
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
# This command requires in the client or route subcommands,
|
|
# the first argument after the policy/action is a dst.
|
|
|
|
local servers="localhost" \
|
|
action="${1}"
|
|
shift
|
|
|
|
if [ "${type}" = "client" -o ! "${work_cmd}" = "interface" ]
|
|
then
|
|
case "${1}" in
|
|
dst|DST|destination|DESTINATION)
|
|
shift
|
|
servers="${1}"
|
|
shift
|
|
;;
|
|
|
|
*)
|
|
error "Please re-phrase to: ${type} nfs ${action} dst <NFS_SERVER> [other rules]"
|
|
return 1
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
local x=
|
|
for x in ${servers}
|
|
do
|
|
local tmp="`${MKTEMP_CMD} ${FIREHOL_DIR}/firehol-rpcinfo-XXXXXXXXXX`"
|
|
|
|
set_work_function "Getting RPC information from server '${x}'"
|
|
|
|
rpcinfo -p ${x} >"${tmp}"
|
|
if [ $? -gt 0 -o ! -s "${tmp}" ]
|
|
then
|
|
error "Cannot get rpcinfo from host '${x}' (using the previous firewall rules)"
|
|
${RM_CMD} -f "${tmp}"
|
|
return 1
|
|
fi
|
|
|
|
local server_rquotad_ports="`${CAT_CMD} "${tmp}" | ${GREP_CMD} " rquotad$" | ( while read a b proto port s; do echo "$proto/$port"; done ) | ${SORT_CMD} | ${UNIQ_CMD}`"
|
|
local server_mountd_ports="`${CAT_CMD} "${tmp}" | ${GREP_CMD} " mountd$" | ( while read a b proto port s; do echo "$proto/$port"; done ) | ${SORT_CMD} | ${UNIQ_CMD}`"
|
|
local server_lockd_ports="`${CAT_CMD} "${tmp}" | ${GREP_CMD} " nlockmgr$" | ( while read a b proto port s; do echo "$proto/$port"; done ) | ${SORT_CMD} | ${UNIQ_CMD}`"
|
|
local server_statd_ports="`${CAT_CMD} "${tmp}" | ${GREP_CMD} " status$" | ( while read a b proto port s; do echo "$proto/$port"; done ) | ${SORT_CMD} | ${UNIQ_CMD}`"
|
|
local server_nfsd_ports="`${CAT_CMD} "${tmp}" | ${GREP_CMD} " nfs$" | ( while read a b proto port s; do echo "$proto/$port"; done ) | ${SORT_CMD} | ${UNIQ_CMD}`"
|
|
|
|
test -z "${server_mountd_ports}" && error "Cannot find mountd ports for nfs server '${x}'" && return 1
|
|
test -z "${server_lockd_ports}" && error "Cannot find lockd ports for nfs server '${x}'" && return 1
|
|
test -z "${server_statd_ports}" && error "Cannot find statd ports for nfs server '${x}'" && return 1
|
|
test -z "${server_nfsd_ports}" && error "Cannot find nfsd ports for nfs server '${x}'" && return 1
|
|
|
|
local dst=
|
|
if [ ! "${x}" = "localhost" ]
|
|
then
|
|
dst="dst ${x}"
|
|
fi
|
|
|
|
if [ ! -z "${server_rquotad_ports}" ]
|
|
then
|
|
set_work_function "Rules for rquotad on server '${x}'"
|
|
rules_custom "${mychain}" "${type}" nfs-rquotad "${server_rquotad_ports}" "500:65535" "${action}" $dst "${@}"
|
|
fi
|
|
|
|
set_work_function "Rules for mountd on server '${x}'"
|
|
rules_custom "${mychain}" "${type}" nfs-mountd "${server_mountd_ports}" "500:65535" "${action}" $dst "${@}"
|
|
|
|
set_work_function "Rules for lockd on server '${x}'"
|
|
rules_custom "${mychain}" "${type}" nfs-lockd "${server_lockd_ports}" "500:65535" "${action}" $dst "${@}"
|
|
|
|
set_work_function "Rules for statd on server '${x}'"
|
|
rules_custom "${mychain}" "${type}" nfs-statd "${server_statd_ports}" "500:65535" "${action}" $dst "${@}"
|
|
|
|
set_work_function "Rules for nfsd on server '${x}'"
|
|
rules_custom "${mychain}" "${type}" nfs-nfsd "${server_nfsd_ports}" "500:65535" "${action}" $dst "${@}"
|
|
|
|
${RM_CMD} -f "${tmp}"
|
|
|
|
echo >&2 ""
|
|
echo >&2 "WARNING:"
|
|
echo >&2 "This firewall must be restarted if NFS server ${x} is restarted!"
|
|
echo >&2 ""
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# --- NIS ----------------------------------------------------------------------
|
|
# These rules work for client access only!
|
|
#
|
|
# Pushing changes to slave servers won't work if these rules are active
|
|
# somewhere between the master and its slaves, because it is impossible to
|
|
# predict the ports where "yppush" will be listening on each push.
|
|
#
|
|
# Pulling changes directly on the slaves will work, and could be improved
|
|
# performance-wise if these rules are modified to open "fypxfrd". This wasn't
|
|
# done because it doesn't make that much sense since pushing changes on the
|
|
# master server is the most common, and recommended, way to replicate maps.
|
|
#
|
|
# Created by Carlos Rodrigues <crlf@users.sourceforge.net>
|
|
# Feature Requests item #1050951 <https://sourceforge.net/tracker/?func=detail&atid=487695&aid=1050951&group_id=58425>
|
|
|
|
rules_nis() {
|
|
local in=in out=out \
|
|
client_ports="${DEFAULT_CLIENT_PORTS}" \
|
|
mychain="${1}" \
|
|
type="${2}"
|
|
shift 2
|
|
|
|
if [ "${type}" = "client" ]
|
|
then
|
|
in=out
|
|
out=in
|
|
fi
|
|
|
|
if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
|
|
then
|
|
client_ports="${LOCAL_CLIENT_PORTS}"
|
|
fi
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
# This command requires in the client or route subcommands,
|
|
# the first argument after the policy/action is a dst.
|
|
|
|
local servers="localhost" \
|
|
action="${1}"
|
|
shift
|
|
|
|
if [ "${type}" = "client" -o ! "${work_cmd}" = "interface" ]
|
|
then
|
|
case "${1}" in
|
|
dst|DST|destination|DESTINATION)
|
|
shift
|
|
servers="${1}"
|
|
shift
|
|
;;
|
|
|
|
*)
|
|
error "Please re-phrase to: ${type} nis ${action} dst <NIS_SERVER> [other rules]"
|
|
return 1
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
local x=
|
|
for x in ${servers}
|
|
do
|
|
local tmp="`${MKTEMP_CMD} ${FIREHOL_DIR}/firehol-rpcinfo-XXXXXXXXXX)`"
|
|
|
|
set_work_function "Getting RPC information from server '${x}'"
|
|
|
|
rpcinfo -p ${x} >"${tmp}"
|
|
if [ $? -gt 0 -o ! -s "${tmp}" ]
|
|
then
|
|
error "Cannot get rpcinfo from host '${x}' (using the previous firewall rules)"
|
|
${RM_CMD} -f "${tmp}"
|
|
return 1
|
|
fi
|
|
|
|
local server_ypserv_ports="`${CAT_CMD} "${tmp}" | ${GREP_CMD} " ypserv$" | ( while read a b proto port s; do echo "$proto/$port"; done ) | ${SORT_CMD} | ${UNIQ_CMD}`"
|
|
local server_yppasswdd_ports="`${CAT_CMD} "${tmp}" | ${GREP_CMD} " yppasswdd$" | ( while read a b proto port s; do echo "$proto/$port"; done ) | ${SORT_CMD} | ${UNIQ_CMD}`"
|
|
|
|
test -z "${server_ypserv_ports}" && error "Cannot find ypserv ports for nis server '${x}'" && return 1
|
|
|
|
local dst=
|
|
if [ ! "${x}" = "localhost" ]
|
|
then
|
|
dst="dst ${x}"
|
|
fi
|
|
|
|
if [ ! -z "${server_yppasswdd_ports}" ]
|
|
then
|
|
set_work_function "Rules for yppasswd on server '${x}'"
|
|
rules_custom "${mychain}" "${type}" nis-yppasswd "${server_yppasswdd_ports}" "500:65535" "${action}" $dst "${@}"
|
|
fi
|
|
|
|
set_work_function "Rules for ypserv on server '${x}'"
|
|
rules_custom "${mychain}" "${type}" nis-ypserv "${server_ypserv_ports}" "500:65535" "${action}" $dst "${@}"
|
|
|
|
${RM_CMD} -f "${tmp}"
|
|
|
|
echo >&2 ""
|
|
echo >&2 "WARNING:"
|
|
echo >&2 "This firewall must be restarted if NIS server ${x} is restarted!"
|
|
echo >&2 ""
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# --- PING ---------------------------------------------------------------------
|
|
|
|
rules_ping() {
|
|
local mychain="${1}" \
|
|
type="${2}"
|
|
shift 2
|
|
|
|
if running_ipv4; then
|
|
ipv4 add_icmp_rule_pair $mychain $type echo-request echo-reply "${@}" || return 1
|
|
fi
|
|
if running_ipv6; then
|
|
ipv6 add_icmpv6_rule_pair $mychain $type echo-request echo-reply "${@}" || return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# --- TIMESTAMP ----------------------------------------------------------------
|
|
|
|
rules_timestamp() {
|
|
local mychain="${1}" \
|
|
type="${2}" \
|
|
status=0
|
|
shift 2
|
|
|
|
if ! push_namespace ipv4; then return 1; fi
|
|
|
|
add_icmp_rule_pair $mychain $type timestamp-request timestamp-reply "${@}" || status=1
|
|
|
|
pop_namespace
|
|
return $status
|
|
}
|
|
|
|
# --- IVP6NEIGH ----------------------------------------------------------------
|
|
|
|
rules_ipv6neigh() {
|
|
local mychain="${1}" \
|
|
type="${2}" \
|
|
status=0
|
|
shift 2
|
|
|
|
if ! push_namespace ipv6; then return 1; fi
|
|
|
|
add_icmpv6_rule_pair_stateless $mychain $type neighbour-solicitation neighbour-advertisement "${@}" || status=1
|
|
|
|
pop_namespace
|
|
return $status
|
|
}
|
|
|
|
# --- IVP6ROUTER ---------------------------------------------------------------
|
|
|
|
rules_ipv6router() {
|
|
local mychain="${1}" \
|
|
type="${2}" \
|
|
status=0
|
|
shift 2
|
|
|
|
if ! push_namespace ipv6; then return 1; fi
|
|
|
|
add_icmpv6_rule_pair_stateless $mychain $type router-solicitation router-advertisement "${@}" || status=1
|
|
|
|
pop_namespace
|
|
return $status
|
|
}
|
|
|
|
# --- IVP6MLD ------------------------------------------------------------------
|
|
|
|
rules_ipv6mld() {
|
|
local mychain="${1}" \
|
|
type="${2}" \
|
|
status=0
|
|
shift 2
|
|
|
|
if ! push_namespace ipv6; then return 1; fi
|
|
|
|
# 130 Multicast Listener Query
|
|
# 131 Multicast Listener Report
|
|
# 132 Multicast Listener Done
|
|
# 143 Version 2 Multicast Listener Report
|
|
add_icmpv6_rule_pair_stateless $mychain $type "131 132 143" 130 "${@}" || status=1
|
|
|
|
pop_namespace
|
|
return $status
|
|
}
|
|
|
|
# --- IVP6ERROR ----------------------------------------------------------------
|
|
|
|
rules_ipv6error() {
|
|
local mychain="${1}" \
|
|
type="${2}"
|
|
shift 2
|
|
|
|
warning "ipv6error service deprecated - no longer useful and will be removed in 4.0.0"
|
|
|
|
return 0
|
|
}
|
|
|
|
# --- ANYSTATELESS -------------------------------------------------------------
|
|
|
|
rules_anystateless() {
|
|
local in=in out=out \
|
|
client_ports="${DEFAULT_CLIENT_PORTS}" \
|
|
mychain="${1}" \
|
|
type="${2}" \
|
|
name="${3}"
|
|
shift 3
|
|
|
|
if [ "${FIREHOL_RULESET_MODE}" = "optimal" ]
|
|
then
|
|
# FIXME
|
|
# We could insert an untrack rule in raw table to make this stateless
|
|
error "Stateless rules are not supported in 'optimal' mode. Please set FIREHOL_RULESET_MODE='accurate'."
|
|
return 1
|
|
fi
|
|
|
|
if [ "${type}" = "client" ]
|
|
then
|
|
in=out
|
|
out=in
|
|
fi
|
|
|
|
if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
|
|
then
|
|
client_ports="${LOCAL_CLIENT_PORTS}"
|
|
fi
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
# allow new and established incoming packets
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" || return 1
|
|
|
|
# allow outgoing established packets
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" || return 1
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# --- MULTICAST ----------------------------------------------------------------
|
|
|
|
rules_multicast() {
|
|
local in=in out=out \
|
|
client_ports="${DEFAULT_CLIENT_PORTS}" \
|
|
mychain="${1}" \
|
|
type="${2}"
|
|
shift 2
|
|
|
|
if [ "${type}" = "client" ]
|
|
then
|
|
in=out
|
|
out=in
|
|
fi
|
|
|
|
if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
|
|
then
|
|
client_ports="${LOCAL_CLIENT_PORTS}"
|
|
fi
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
# match multicast packets in both directions
|
|
rule ${out} action "${@}" chain "${out}_${mychain}" dst "${MULTICAST_IPS}" proto 2 || return 1
|
|
rule ${in} reverse action "${@}" chain "${in}_${mychain}" src "${MULTICAST_IPS}" proto 2 || return 1
|
|
|
|
rule ${out} action "${@}" chain "${out}_${mychain}" dst "${MULTICAST_IPS}" proto udp || return 1
|
|
rule ${in} reverse action "${@}" chain "${in}_${mychain}" src "${MULTICAST_IPS}" proto udp || return 1
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# --- CUSTOM -------------------------------------------------------------------
|
|
|
|
rules_custom() {
|
|
local in=in out=out \
|
|
client_ports="${DEFAULT_CLIENT_PORTS}" \
|
|
mychain="${1}" \
|
|
type="${2}" \
|
|
server="${3}" \
|
|
my_server_ports="${4}" \
|
|
my_client_ports="${5}" \
|
|
helpers= x= proto= protocols= sp= sport= cport= \
|
|
require_ct=0
|
|
shift 5
|
|
|
|
if [ "$1" = "helpers" ]
|
|
then
|
|
helpers="$2"
|
|
shift 2
|
|
fi
|
|
|
|
if [ "${type}" = "client" ]
|
|
then
|
|
in=out
|
|
out=in
|
|
fi
|
|
|
|
if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
|
|
then
|
|
client_ports="${LOCAL_CLIENT_PORTS}"
|
|
fi
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
# do we have to create CT entries?
|
|
if [ "${FIREHOL_CONNTRACK_HELPERS_ASSIGNMENT}" = "firehol" -a ! -z "${helpers}" ]
|
|
then
|
|
for x in ${helpers}
|
|
do
|
|
case "${x}" in
|
|
sip|ftp|tftp|sane)
|
|
require_ct=1
|
|
;;
|
|
|
|
pptp|irc)
|
|
running_ipv4 && require_ct=1
|
|
running_ipv6 && require_ct=0
|
|
;;
|
|
|
|
h323|proto_gre|amanda|netbios_ns)
|
|
require_ct=0
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ ${require_ct} -eq 1 ]
|
|
then
|
|
# reconstruct the path of flow in the 'raw' table
|
|
|
|
if [ "${work_cmd}" = "interface" ]
|
|
then
|
|
reconstruct_flow_inheritance in raw PREROUTING outface any
|
|
reconstruct_flow_inheritance out raw OUTPUT inface any
|
|
else
|
|
reconstruct_flow_inheritance in raw PREROUTING outface any
|
|
reconstruct_flow_inheritance out raw PREROUTING outface any
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# find all protocols used by the service
|
|
for x in ${my_server_ports}
|
|
do
|
|
proto="${x//\/*/}"
|
|
protocols="|${protocols//|${proto}|/}|${proto}|"
|
|
done
|
|
|
|
# find all client ports
|
|
cport="${my_client_ports//default/${client_ports}}"
|
|
|
|
# generate one set of rules per protocol (all server and clients ports used by the protocol)
|
|
for proto in ${protocols//|/ }
|
|
do
|
|
# find all the server ports of this protocol
|
|
sport=
|
|
for sp in ${my_server_ports}
|
|
do
|
|
[ "${sp//\/*/}" = "${proto}" ] && sport="${sport} ${sp//*\//}"
|
|
done
|
|
|
|
set_work_function "Rules for ${server} ${type}, with server port(s) '${proto}/${sport}' and client port(s) '${cport}'"
|
|
|
|
# allow new and established incoming packets
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" proto "${proto}" sport "${cport}" dport "${sport}" state NEW,ESTABLISHED || return 1
|
|
|
|
# allow outgoing established packets
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto "${proto}" sport "${cport}" dport "${sport}" state ESTABLISHED || return 1
|
|
|
|
if [ ${require_ct} -eq 1 ]
|
|
then
|
|
for x in ${helpers}
|
|
do
|
|
# configure the helper
|
|
# this is the same with the request and the reply, but with action CT.
|
|
|
|
set_work_function "Rules for configuring helper '${x}'"
|
|
|
|
# FIXME
|
|
# for each helper we should find out which packet determines the RELATED socket
|
|
# so that we will not match both client->server and server->client, but only one
|
|
# of the two. This will further limit the security threat due to helper use.
|
|
|
|
rule table raw ${in} action "${@}" chain "${in}_${mychain}" proto "${proto}" sport "${cport}" dport "${sport}" nosoftwarnings action CT helper ${x} || return 1
|
|
rule table raw ${out} reverse action "${@}" chain "${out}_${mychain}" proto "${proto}" sport "${cport}" dport "${sport}" nosoftwarnings action CT helper ${x} || return 1
|
|
done
|
|
fi
|
|
done
|
|
|
|
# generate the helper rules to match RELATED traffic
|
|
for x in ${helpers}
|
|
do
|
|
set_work_function "Rules for RELATED packets to ${server} ${type}, using helper '${x}'"
|
|
|
|
# match RELATED packets
|
|
# we do not match server and client ports here, because the RELATED packet we are trying
|
|
# to match may not be on these ports
|
|
|
|
# FIXME
|
|
# the above FIXME note applies here too: We should know what we expect to match, per helper.
|
|
# The way it is implemented now, we inherit src/dst and inface/outface (since the rules below
|
|
# are implemented in the chain of the actual service rules, but since we match both directions
|
|
# of traffic, we may have allowed also spoofed packets to be matched as RELATED - it is totaly
|
|
# up to the helper to figure out if the traffic we see is really RELATED to an ESTABLISHED
|
|
# socket or not).
|
|
|
|
# Note
|
|
# ESTABLISHED connections matching is required at this point, because the replies of RELATED sockets
|
|
# will not be accepted by the connection tracker. Example: ftp (test without client all accept)
|
|
|
|
rule ${in} action "${@}" chain "${in}_${mychain}" helper ${x} state RELATED,ESTABLISHED || return 1
|
|
rule ${out} reverse action "${@}" chain "${out}_${mychain}" helper ${x} state RELATED,ESTABLISHED || return 1
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# SUPPORT FOR EXTERNAL DEFINITIONS OF SERVICES
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Load all the services.
|
|
# All these files should start with: #FHVER: 1
|
|
cd "${FIREHOL_SERVICES_DIR}" || exit 1
|
|
for f in `$LS_CMD *.conf 2>/dev/null`
|
|
do
|
|
cd "${FIREHOL_SERVICES_DIR}" || exit 1
|
|
|
|
if [ ! -O "${f}" ]
|
|
then
|
|
echo >&2 " WARNING >>> Ignoring service in '${FIREHOL_SERVICES_DIR}/${f}' because it is not owned by root."
|
|
continue
|
|
fi
|
|
|
|
n=`${HEAD_CMD} -n 1 "${f}" | ${CUT_CMD} -d ':' -f 2`
|
|
${EXPR_CMD} ${n} + 0 >/dev/null 2>&1
|
|
if [ $? -ne 0 ]
|
|
then
|
|
echo >&2 " WARNING >>> Ignoring service in '${FIREHOL_SERVICES_DIR}/${f}' due to malformed header."
|
|
elif [ ${n} -ne ${FIREHOL_SERVICES_API} ]
|
|
then
|
|
echo >&2 " WARNING >>> Ignoring service '${FIREHOL_SERVICES_DIR}/${f}' due to incompatible API version."
|
|
else
|
|
n=`${HEAD_CMD} -n 1 "${f}" | ${CUT_CMD} -d ':' -f 3`
|
|
${EXPR_CMD} ${n} + 0 >/dev/null 2>&1
|
|
if [ $? -ne 0 ]
|
|
then
|
|
echo >&2 " WARNING >>> Ignoring service in '${FIREHOL_SERVICES_DIR}/${f}' due to malformed API minor number."
|
|
else
|
|
source ${f}
|
|
ret=$?
|
|
if [ ${ret} -ne 0 ]
|
|
then
|
|
echo >&2 " WARNING >>> Service in '${FIREHOL_SERVICES_DIR}/${f}' returned code ${ret}."
|
|
continue
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
cd "${PROGRAM_PWD}" || exit 1
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# The caller may need just our services definitions
|
|
if [ "$1" = "gimme-the-services-defs" ]
|
|
then
|
|
return 0
|
|
exit 1
|
|
fi
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# HELPER FUNCTIONS BELOW THIS POINT
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
|
|
FIREHOL_COMMAND_COUNTER=0
|
|
iptables() {
|
|
|
|
# if the caller is not from the program file, get the config line calling us
|
|
[ ! "${BASH_SOURCE[1]}" = "${PROGRAM_FILE}" ] && work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
if [ $FIREHOL_FAST_ACTIVATION -eq 1 ]
|
|
then
|
|
run_fast iptables "${@}"
|
|
else
|
|
postprocess -ns ${IPTABLES_CMD} "${@}"
|
|
fi
|
|
FIREHOL_COMMAND_COUNTER=$[FIREHOL_COMMAND_COUNTER + 1]
|
|
|
|
return 0
|
|
}
|
|
|
|
ip6tables() {
|
|
# if the caller is not from the program file, get the config line calling us
|
|
[ ! "${BASH_SOURCE[1]}" = "${PROGRAM_FILE}" ] && work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
if [ $FIREHOL_FAST_ACTIVATION -eq 1 ]
|
|
then
|
|
run_fast ip6tables "${@}"
|
|
else
|
|
postprocess -ns ${IP6TABLES_CMD} "${@}"
|
|
fi
|
|
FIREHOL_COMMAND_COUNTER=$[FIREHOL_COMMAND_COUNTER + 1]
|
|
|
|
return 0
|
|
}
|
|
|
|
iptables_both() {
|
|
if running_ipv4; then iptables "${@}" || return; fi
|
|
if running_ipv6; then ip6tables "${@}" || return; fi
|
|
return 0
|
|
}
|
|
|
|
ipset_file_to_restore_filter() {
|
|
local name="${1}" ipv="${2}" hash="${3}"
|
|
shift 3
|
|
|
|
if [ ${FIREHOL_HAVE_IPRANGE} -eq 1 -a "${ipv}" = "ipv4" -a \( "${hash}" = "hash:net" -o "${hash}" = "nethash" -o "${hash}" = "hash:ip" -o "${hash}" = "iphash" \) ]
|
|
then
|
|
local opts=
|
|
[ "${hash}" = "hash:net" -o "${hash}" = "nethash" ] && opts="--ipset-reduce 20 --ipset-reduce-entries 65536"
|
|
[ "${hash}" = "hash:ip" -o "${hash}" = "iphash" ] && opts="-1"
|
|
${IPRANGE_CMD} ${opts} \
|
|
--print-prefix "${IPSET_ADD_OPTION} ${name} " \
|
|
--print-suffix " ${*}"
|
|
else
|
|
${SORT_CMD} -u |\
|
|
while read
|
|
do
|
|
echo "${IPSET_ADD_OPTION} ${name} ${REPLY} ${*}"
|
|
done
|
|
fi
|
|
}
|
|
|
|
ipset_addfile() {
|
|
local name="${1}" file= opts= filter="${CAT_CMD}" ipv_match="^[0-9a-fA-F\.:/\-]+$" ipv= hash=
|
|
shift
|
|
|
|
hash="${FIREHOL_IPSETS_HASH[${name}]}"
|
|
ipv="${FIREHOL_IPSETS_IPV[${name}]}"
|
|
case "${ipv}" in
|
|
ipv4) ipv_match="^[0-9\./\-]+$";;
|
|
ipv6) ipv_match="^[0-9a-fA-F:/\-]+$";;
|
|
esac
|
|
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1}" in
|
|
ip|ips) filter="${GREP_CMD} -v /";;
|
|
net|nets) filter="${GREP_CMD} /";;
|
|
ipv4) ;; # backward compatibility
|
|
ipv6) ;; # backward compatibility
|
|
*) break;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
file="${1}"
|
|
shift
|
|
opts="${*}"
|
|
|
|
[ ! -f "${file}" ] && file="${FIREHOL_CONFIG_DIR}/${file}"
|
|
if [ ! -f "${file}" ]
|
|
then
|
|
error "${FUNCNAME}: Cannot find file '${file}'."
|
|
return 1
|
|
fi
|
|
|
|
# cleanup the file
|
|
${CAT_CMD} "${file}" |\
|
|
${SED_CMD} -e "s/#.*$//g" -e "s/[\t ]\+//g" |\
|
|
${EGREP_CMD} "${ipv_match}" |\
|
|
${filter} |\
|
|
ipset_file_to_restore_filter "${name}" "${ipv}" "${hash}" ${opts}
|
|
}
|
|
|
|
ipset_warning() {
|
|
if [ ${IPSET_WARNING} -eq 1 ]
|
|
then
|
|
warning "ipset is requested, but ipset is not installed. Firewall may not be able to be activated."
|
|
IPSET_WARNING=0
|
|
fi
|
|
}
|
|
|
|
iprange_warning() {
|
|
if [ ${IPRANGE_WARNING} -eq 1 ]
|
|
then
|
|
warning "iprange command is not installed - ipsets will not be optimal."
|
|
IPRANGE_WARNING=0
|
|
fi
|
|
}
|
|
|
|
ipset_list_active_names() {
|
|
eval "${IPSET_CMD} ${IPSET_LIST_NAMES_EVAL}"
|
|
}
|
|
|
|
ipset_save_active_to_spool() {
|
|
# this is extremely slow when there are big ipsets in memory
|
|
#
|
|
# ${IPSET_CMD} ${IPSET_SAVE_OPTION} >"${FIREHOL_SPOOL_DIR}/last.ipset.save"
|
|
:
|
|
}
|
|
|
|
# keep track of all the ipsets the firewall uses
|
|
declare -A FIREHOL_IPSETS_USED=()
|
|
declare -A FIREHOL_IPSETS_IPV=()
|
|
declare -A FIREHOL_IPSETS_KEEP=()
|
|
FIREHOL_IPSETS_RESPECT_KEEP=1
|
|
|
|
declare -A FIREHOL_IPSETS_HASH=()
|
|
declare -A FIREHOL_IPSETS_OPTIONS=()
|
|
declare -A FIREHOL_IPSETS_MAXELEM=()
|
|
|
|
# this is a wrapper around ipset
|
|
# it has the same syntax
|
|
ipset4() { ipv4 ipset "${@}"; }
|
|
ipset6() { ipv6 ipset "${@}"; }
|
|
ipset46() { both ipset "${@}"; }
|
|
ipset() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
if [ ${ENABLE_IPSET} -ne 1 ]
|
|
then
|
|
error "ipset is not enabled. Do you have ipset installed?"
|
|
return 1
|
|
fi
|
|
|
|
local cmd="${1}" name="${2}"
|
|
shift 2
|
|
|
|
if [ "z${IPSET_ADD_OPTION:0:2}" = "z-!" ]
|
|
then
|
|
error "Your IPSET_ADD_OPTION has -! in it. This will make your IPs not to be added to the ipset. Please remove it. You can add -! to the IPSET_RESTORE_OPTION to accept duplicate IPs without error."
|
|
return 1
|
|
fi
|
|
|
|
case "${cmd}" in
|
|
create|-N|--create)
|
|
local hash="${1}" inet= opts=
|
|
shift
|
|
|
|
if [ ! -z "${FIREHOL_IPSETS_USED[$name]}" ]
|
|
then
|
|
error "ipset ${name} already exists."
|
|
return 1
|
|
fi
|
|
|
|
if running_both
|
|
then
|
|
error "Cannot run ipset for both IPv4 and IPv6 at the same time."
|
|
return 1
|
|
elif running_ipv6
|
|
then
|
|
inet="${IPSET_CREATE_IPV6_OPTION}"
|
|
FIREHOL_IPSETS_IPV[$name]="ipv6"
|
|
else
|
|
FIREHOL_IPSETS_IPV[$name]="ipv4"
|
|
fi
|
|
|
|
FIREHOL_IPSETS_MAXELEM[$name]=0
|
|
FIREHOL_IPSETS_KEEP[$name]=0
|
|
opts=
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1}" in
|
|
maxelem)
|
|
FIREHOL_IPSETS_MAXELEM[$name]="${2}"
|
|
shift
|
|
;;
|
|
|
|
prevent_reset_on_restart)
|
|
FIREHOL_IPSETS_KEEP[$name]=1
|
|
;;
|
|
|
|
*)
|
|
opts="${opts} ${1}"
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
FIREHOL_IPSETS_OPTIONS[$name]="${inet} ${opts}"
|
|
FIREHOL_IPSETS_HASH[$name]="${hash}"
|
|
|
|
FIREHOL_IPSETS_USED[$name]="CREATED"
|
|
set_work_function "Created ipset ${name} of type ${FIREHOL_IPSETS_HASH[$name]} with options: ${FIREHOL_IPSETS_OPTIONS[$name]}"
|
|
;;
|
|
|
|
add|-A|--add)
|
|
if [ ! "${FIREHOL_IPSETS_USED[$name]}" = "CREATED" ]
|
|
then
|
|
error "${FUNCNAME}: Cannot add IPs to ipset '${name}'. The ipset must be created first."
|
|
return 1
|
|
fi
|
|
|
|
local ip="${1}" x=
|
|
shift
|
|
|
|
if [ "${FIREHOL_IPSETS_IPV[$name]}" = "ipv6" ]
|
|
then
|
|
ip=${ip//reserved_ips()/${RESERVED_IPV6}}
|
|
ip=${ip//private_ips()/${PRIVATE_IPV6}}
|
|
ip=${ip//multicast_ips()/${MULTICAST_IPV6}}
|
|
ip=${ip//unroutable_ips()/${UNROUTABLE_IPV6}}
|
|
else
|
|
ip=${ip//reserved_ips()/${RESERVED_IPV4}}
|
|
ip=${ip//private_ips()/${PRIVATE_IPV4}}
|
|
ip=${ip//multicast_ips()/${MULTICAST_IPV4}}
|
|
ip=${ip//unroutable_ips()/${UNROUTABLE_IPV4}}
|
|
fi
|
|
|
|
for x in ${ip//,/ }
|
|
do
|
|
echo "${IPSET_ADD_OPTION} ${name} ${x} ${*}" >>"${FIREHOL_DIR}/ipset.${name}.rules"
|
|
done
|
|
;;
|
|
|
|
addfile|--addfile)
|
|
if [ ! "${FIREHOL_IPSETS_USED[$name]}" = "CREATED" ]
|
|
then
|
|
error "${FUNCNAME}: Cannot add IPs to ipset '${name}'. The ipset must be created first."
|
|
return 1
|
|
fi
|
|
|
|
ipset_addfile "${name}" "${@}" >>"${FIREHOL_DIR}/ipset.${name}.rules" || return 1
|
|
;;
|
|
|
|
*)
|
|
test -z "${FIREHOL_IPSETS_USED[$name]}" && FIREHOL_IPSETS_USED[$name]="USED"
|
|
|
|
postprocess ${IPSET_CMD} ${cmd} ${name} "${@}"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
ipset_apply() {
|
|
local name="${1}" file="${2}" swap="${3}" entries=0 opts= hash= tmpname=
|
|
|
|
#echo >&2 "Applying ipset ${name} from ${file} with options: ${swap}..."
|
|
|
|
echo "COMMIT" >>"${file}"
|
|
|
|
if [ "${swap}" = "swap" ]
|
|
then
|
|
# find a temporary name for the new ipset
|
|
FIREHOL_IPSET_TMP_COUNTER=$[ FIREHOL_IPSET_TMP_COUNTER + 1 ]
|
|
tmpname="tmp-$$-${RANDOM}-${FIREHOL_IPSET_TMP_COUNTER}"
|
|
FIREHOL_IPSET_TMP_SETS[$tmpname]=1
|
|
else
|
|
tmpname="${name}"
|
|
fi
|
|
|
|
hash="${FIREHOL_IPSETS_HASH[$name]}"
|
|
opts="${FIREHOL_IPSETS_OPTIONS[$name]}"
|
|
|
|
if [ "${hash}" = "iphash" -o "${hash}" = "hash:ip" -o "${hash}" = "nethash" -o "${hash}" = "hash:net" ]
|
|
then
|
|
# count the entries in the file
|
|
entries=$($WC_CMD -l <"${file}")
|
|
|
|
# if the user gave maxelem, keep the max
|
|
if [ ${entries} -lt ${FIREHOL_IPSETS_MAXELEM[$name]} ]
|
|
then
|
|
entries=${FIREHOL_IPSETS_MAXELEM[$name]}
|
|
fi
|
|
|
|
# if the entries is above the default, or the user gave something
|
|
if [ ${entries} -gt 65536 -o ${FIREHOL_IPSETS_MAXELEM[$name]} -gt 0 ]
|
|
then
|
|
opts="${opts} maxelem ${entries}"
|
|
fi
|
|
elif [ ${FIREHOL_IPSETS_MAXELEM[$name]} -gt 0 ]
|
|
then
|
|
# the user gave something, respect it
|
|
opts="${opts} maxelem ${FIREHOL_IPSETS_MAXELEM[$name]}"
|
|
fi
|
|
|
|
${IPSET_CMD} ${IPSET_CREATE_OPTION} ${tmpname} ${FIREHOL_IPSETS_HASH[$name]} ${opts} || return 1
|
|
${IPSET_CMD} ${IPSET_FLUSH_OPTION} ${tmpname} || return 1
|
|
|
|
if [ ! "${swap}" = "swap" ]
|
|
then
|
|
${IPSET_CMD} ${IPSET_RESTORE_OPTION} <"${file}"
|
|
[ $? -ne 0 ] && return 1
|
|
else
|
|
# give the temporary name to the set
|
|
${SED_CMD} <"${file}" \
|
|
-e "s|^\([^[:space:]]*\) ${name} \(.*\)|\1 ${tmpname} \2|g" \
|
|
-e "s|^\([^[:space:]]*\) ${name}$|\1 ${tmpname}|g" |\
|
|
${IPSET_CMD} ${IPSET_RESTORE_OPTION}
|
|
[ $? -ne 0 ] && return 1
|
|
|
|
# swap them, to activate the temporary ipset
|
|
${IPSET_CMD} ${IPSET_SWAP_OPTION} ${tmpname} ${name} || return 1
|
|
|
|
# destroy the temporary ipset
|
|
${IPSET_CMD} ${IPSET_DESTROY_OPTION} ${tmpname} || return 1
|
|
fi
|
|
|
|
# keep the file, if it is not our spool file
|
|
if [ ! "${file}" -ef "${FIREHOL_SPOOL_DIR}/ipset.${name}.rules" ]
|
|
then
|
|
$CP_CMD "${file}" "${FIREHOL_SPOOL_DIR}/ipset.${name}.rules"
|
|
fi
|
|
}
|
|
|
|
ipsets_apply_all() {
|
|
local from="${1}" base="${FIREHOL_DIR}" restoring=0 x= swap=
|
|
|
|
# if we have nothing to do, return
|
|
# if we are called with 'spool', FIREHOL_IPSETS_USED is empty and we will load it from the spool file
|
|
# otherwise, if FIREHOL_IPSETS_USED is empty and the mode is START, there is nothing to be done
|
|
[ ! "${from}" = "spool" -a "${#FIREHOL_IPSETS_USED[@]}" -eq 0 ] && return 0
|
|
|
|
if [ ${ENABLE_IPSET} -ne 1 ]
|
|
then
|
|
error "ipset is not enabled. Do you have ipset installed?"
|
|
return 1
|
|
fi
|
|
|
|
if [ "${from}" = "spool" ]
|
|
then
|
|
# If $from=spool, we are restoring ipsets
|
|
# when we are restoring, we only add new ipsets
|
|
# we will not alter existing ipsets
|
|
|
|
base="${FIREHOL_SPOOL_DIR}"
|
|
restoring=1
|
|
|
|
if [ -f "${FIREHOL_SPOOL_DIR}/ipsets.conf" ]
|
|
then
|
|
source "${FIREHOL_SPOOL_DIR}/ipsets.conf"
|
|
if [ $? -ne 0 ]
|
|
then
|
|
warning "Cannot load ${FIREHOL_SPOOL_DIR}/ipsets.conf"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
progress "Restoring ipsets from '${FIREHOL_SPOOL_DIR}'"
|
|
else
|
|
progress "Activating ipsets"
|
|
|
|
# when we are activating the firewall, we will overwrite
|
|
# existing ipsets.
|
|
fi
|
|
|
|
# take a list of all active ipsets
|
|
# and mark each one that we have too as existing
|
|
for x in $( ipset_list_active_names )
|
|
do
|
|
if [ "${FIREHOL_IPSETS_USED[$x]}" = "CREATED" ]
|
|
then
|
|
FIREHOL_IPSETS_USED[$x]="EXISTS"
|
|
fi
|
|
done
|
|
|
|
for x in ${!FIREHOL_IPSETS_USED[@]}
|
|
do
|
|
swap=
|
|
|
|
# did we had an ipset helper for this ipset?
|
|
[ "${FIREHOL_IPSETS_USED[$x]}" = "USED" ] && continue
|
|
|
|
# shall we restore this ipset?
|
|
if [ "${FIREHOL_IPSETS_USED[$x]}" = "EXISTS" ]
|
|
then
|
|
[ ${FIREHOL_IPSETS_RESPECT_KEEP} -eq 1 -a "${FIREHOL_IPSETS_KEEP[$x]}" = "1" ] && continue
|
|
swap="swap"
|
|
fi
|
|
|
|
# restore the flag
|
|
FIREHOL_IPSETS_USED[$x]="CREATED"
|
|
|
|
# apply it
|
|
ipset_apply "${x}" "${base}/ipset.${x}.rules" ${swap}
|
|
if [ $? -ne 0 ]
|
|
then
|
|
ipset_remove_all_tmp_sets
|
|
return 1
|
|
fi
|
|
done
|
|
|
|
ipset_done_all_tmp_sets
|
|
success
|
|
|
|
if [ ! "${from}" = "spool" ]
|
|
then
|
|
declare -p FIREHOL_IPSETS_USED FIREHOL_IPSETS_IPV FIREHOL_IPSETS_KEEP FIREHOL_IPSETS_HASH FIREHOL_IPSETS_OPTIONS FIREHOL_IPSETS_MAXELEM >"${FIREHOL_SPOOL_DIR}/ipsets.conf"
|
|
ipset_save_active_to_spool
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
setup_lo_rpfilter=0
|
|
declare -A setup_lo_route_interfaces=()
|
|
setup_lo_for_synproxy() {
|
|
|
|
# disable rpfilter for lo
|
|
if [ ${setup_lo_rpfilter} -eq 0 ]
|
|
then
|
|
postprocess -warn ${SYSCTL_CMD} -w net.ipv4.conf.lo.rp_filter=0
|
|
setup_lo_rpfilter=1
|
|
fi
|
|
|
|
local x=
|
|
for x in "${@}"
|
|
do
|
|
if [ -z "${setup_lo_route_interfaces[$x]}" ]
|
|
then
|
|
postprocess -ne ${SYSCTL_CMD} -w net.ipv4.conf.${x}.route_localnet=1
|
|
syslog info "Enabling net.ipv4.conf.${x}.route_localnet=1."
|
|
setup_lo_route_interfaces[$x]=1
|
|
fi
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
finalize_synproxy() {
|
|
local oldns= x= ipvall=
|
|
|
|
[ ${synproxy_hooks_added4} -eq 1 ] && ipvall="${ipvall} ipv4"
|
|
[ ${synproxy_hooks_added6} -eq 1 ] && ipvall="${ipvall} ipv6"
|
|
|
|
for x in ${ipvall}
|
|
do
|
|
oldns="${FIREHOL_NS_CURR}"
|
|
FIREHOL_NS_CURR="${x}"
|
|
|
|
set_work_function -ne "Prevent SYNPROXY->SERVER SYN from traversing the raw table"
|
|
rule table raw chain SYNPROXY2SERVER_PRE action ACCEPT || return 1
|
|
rule table raw chain SYNPROXY2SERVER_OUT action ACCEPT || return 1
|
|
|
|
#set_work_function -ne "Prevent SYNPROXY->SERVER SYN from traversing the mangle table"
|
|
#rule table mangle chain SYNPROXY2SERVER_PRE action ACCEPT || return 1
|
|
#rule table mangle chain SYNPROXY2SERVER_IN action ACCEPT || return 1
|
|
#rule table mangle chain SYNPROXY2SERVER_OUT action ACCEPT || return 1
|
|
#rule table mangle chain SYNPROXY2SERVER_POST action ACCEPT || return 1
|
|
|
|
set_work_function -ne "Prevent SYNPROXY->SERVER SYN from traversing the nat table"
|
|
rule table nat chain SYNPROXY2SERVER_PRE action ACCEPT || return 1
|
|
rule table nat chain SYNPROXY2SERVER_OUT action ACCEPT || return 1
|
|
|
|
set_work_function -ne "Orphan SYN packet from SYNPROXY"
|
|
rule table filter chain SYNPROXY2SERVER_IN action DROP loglimit "BLOCKED ORPHAN SYNPROXY->SERVER filter.IN" || return 1
|
|
rule table filter chain SYNPROXY2SERVER_OUT action DROP loglimit "BLOCKED ORPHAN SYNPROXY->SERVER filter.OUT" || return 1
|
|
|
|
FIREHOL_NS_CURR="${oldns}"
|
|
done
|
|
}
|
|
|
|
synproxy_hooks_added4=0
|
|
synproxy_hooks_added6=0
|
|
synproxy_mark=
|
|
synproxy4() { ipv4 synproxy "${@}"; }
|
|
synproxy6() { ipv6 synproxy "${@}"; }
|
|
synproxy46() { both synproxy "${@}"; }
|
|
synproxy() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
local where="${1}" chain= match=() action= action_args=() log=() \
|
|
inface=() dst=() src=() overwrite_in=() overwrite_out=() \
|
|
x= dohooks=0 owner=()
|
|
shift
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
require_work clear || ( error "${FUNCNAME} cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
|
|
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1,,}" in
|
|
dnat|redirect|accept|reject|drop)
|
|
action="${1^^}"; shift
|
|
action_args=("${@}")
|
|
break
|
|
;;
|
|
|
|
action)
|
|
action="${2}"; shift 2
|
|
action_args=("${@}")
|
|
break
|
|
;;
|
|
|
|
*) match=("${match[@]}" "${1}")
|
|
|
|
# collect things we need later
|
|
case "${1,,}" in
|
|
inface) inface=("${inface[@]}" ${2//,/ }) ;;
|
|
dst|dst4|dst6) dst=("${dst[@]}" ${2//,/ }) ;;
|
|
src|src4|src6) src=("${src[@]}" ${2//,/ }) ;;
|
|
esac
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
# echo >&2 "MATCH: ${match[*]}"
|
|
# echo >&2 "ACTION: ${action} ${action_args[*]}"
|
|
|
|
# if no action is given, assume ACCEPT
|
|
[ -z "${action}" ] && action="ACCEPT"
|
|
|
|
# make sure we have an inface
|
|
if [ -z "${inface[*]}" ]
|
|
then
|
|
error "You should set 'inface' interfaces for SYNPROXY."
|
|
return 1
|
|
fi
|
|
|
|
# make sure we have a dst/dst4/dst6
|
|
if [ ${FIREHOL_SYNPROXY_EXCLUDE_OWNER} -eq 0 -a \( -z "${dst[*]}" -o "${dst[*]}" = "not" \) ]
|
|
then
|
|
error "You should set 'dst' IPs for SYNPROXY."
|
|
return 1
|
|
fi
|
|
|
|
if [ ${FIREHOL_SYNPROXY_EXCLUDE_OWNER} -eq 1 ]
|
|
then
|
|
owner=(user not 0-65535 group not 0-65535)
|
|
|
|
if [ \( -z "${dst[*]}" -o "${dst[*]}" = "not" \) -a ! "${src[*]}" = "not" ]
|
|
then
|
|
error "You have to set a 'dst' or a 'src not' with SYNPROXY."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
if running_both
|
|
then
|
|
error "Cannot setup SYNPROXY for both IPv4 and IPv6 at the same time."
|
|
return 1
|
|
fi
|
|
|
|
if [ ${synproxy_hooks_added4} -eq 0 -a ${synproxy_hooks_added6} -eq 0 ]
|
|
then
|
|
# make sure we have a synproxy mark
|
|
if [ -z "${MARKS_MASKS[synproxy]}" ]
|
|
then
|
|
markdef synproxy 2 temporary stateless || return 1
|
|
fi
|
|
synproxy_mark=$(mark_value synproxy 1)
|
|
fi
|
|
|
|
if running_ipv4;
|
|
then
|
|
test ${synproxy_hooks_added4} -eq 0 && dohooks=1
|
|
synproxy_hooks_added4=1
|
|
fi
|
|
|
|
if running_ipv6;
|
|
then
|
|
test ${synproxy_hooks_added6} -eq 0 && dohooks=1
|
|
synproxy_hooks_added6=1
|
|
fi
|
|
|
|
if [ ${dohooks} -eq 1 ]
|
|
then
|
|
# all these chains are traversed by the packet sent by the SYNPROXY to the SERVER
|
|
# the PREROUTING and INPUT ones are traversed in case of REDIRECT
|
|
|
|
# we will make sure that packets entering these chains do not return back
|
|
# thus, synproxy will not interact with the rest of the firewall.
|
|
|
|
# no state in raw - it is always INVALID.
|
|
create_chain raw SYNPROXY2SERVER_PRE PREROUTING proto tcp rawmark ${synproxy_mark}
|
|
create_chain raw SYNPROXY2SERVER_OUT OUTPUT proto tcp rawmark ${synproxy_mark}
|
|
|
|
# mangle does not harm.
|
|
# it can be used for marking packets which can then be used for NAT, but NAT is blocked for
|
|
# synproxy. we prefer to keep mangling open - it might be needed in several cases.
|
|
|
|
# mangle.INPUT and mangle.POSTROUTING are needed for CONNMARKs
|
|
#create_chain mangle SYNPROXY2SERVER_PRE PREROUTING proto tcp state NEW rawmark ${synproxy_mark}
|
|
#create_chain mangle SYNPROXY2SERVER_IN INPUT proto tcp state NEW rawmark ${synproxy_mark}
|
|
#create_chain mangle SYNPROXY2SERVER_OUT OUTPUT proto tcp state NEW rawmark ${synproxy_mark}
|
|
#create_chain mangle SYNPROXY2SERVER_POST POSTROUTING proto tcp state NEW rawmark ${synproxy_mark}
|
|
|
|
create_chain nat SYNPROXY2SERVER_PRE PREROUTING proto tcp state NEW rawmark ${synproxy_mark}
|
|
create_chain nat SYNPROXY2SERVER_OUT OUTPUT proto tcp state NEW rawmark ${synproxy_mark}
|
|
# we leave nat.POSTROUTING since it mey be need for SNAT
|
|
|
|
create_chain filter SYNPROXY2SERVER_IN INPUT proto tcp state NEW rawmark ${synproxy_mark}
|
|
create_chain filter SYNPROXY2SERVER_OUT OUTPUT proto tcp state NEW rawmark ${synproxy_mark}
|
|
fi
|
|
|
|
set_work_function "CLIENT->SERVER SYN packet untracking at table 'raw'"
|
|
test ${FIREHOL_SYNPROXY_LOG} -eq 1 && log=(loglimit "CLIENT->SYNPROXY SYN NOTRACK")
|
|
rule table raw chain PREROUTING "${match[@]}" proto tcp custom '-m tcp --syn' nosoftwarnings outface any physout any action CT --notrack "${log[@]}" || return 1
|
|
|
|
# based on where the user wants us to hook the SYNPROXY
|
|
for chain in ${where//,/ }
|
|
do
|
|
case "${chain^^}" in
|
|
PRE|PREROUTING) error "SYNPROXY cannot be used in PREROUTING"
|
|
return 1
|
|
;;
|
|
|
|
IN|INPUT) chain="INPUT"
|
|
if [ "${action}" = "DNAT" ]
|
|
then
|
|
overwrite_in=(nosoftwarnings outface any physout any)
|
|
overwrite_out=()
|
|
else
|
|
overwrite_in=(outface any physout any)
|
|
overwrite_out=(outface lo)
|
|
fi
|
|
;;
|
|
|
|
OUT|OUTPUT) error "There is no point to setup SYNPROXY on OUTPUT"
|
|
return 1
|
|
;;
|
|
|
|
PASS|FORWARD) chain="FORWARD"
|
|
overwrite_in=()
|
|
overwrite_out=()
|
|
;;
|
|
|
|
POST|POSTROUTING) error "SYNPROXY cannot be used in POSTROUTING"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
# FIXME
|
|
# most probably we will have to support tcp options per call for this helper
|
|
|
|
set_work_function "CLIENT->SERVER untracked SYN (or ACK) packet intercepted by SYNPROXY at filter.${chain}"
|
|
test ${FIREHOL_SYNPROXY_LOG} -eq 1 && log=(loglimit "CLIENT->SYNPROXY SYN or ACK")
|
|
rule table filter chain ${chain} "${match[@]}" ${overwrite_in[@]} proto tcp state INVALID,UNTRACKED "${log[@]}" action SYNPROXY ${FIREHOL_SYNPROXY_OPTIONS} || return 1
|
|
|
|
set_work_function "SYNPROXY->CLIENT SYN+ACK packet at filter.OUTPUT"
|
|
test ${FIREHOL_SYNPROXY_LOG} -eq 1 && log=(loglimit "SYNPROXY->CLIENT SYN,ACK")
|
|
rule table filter chain OUTPUT reverse custom '-m tcp --tcp-flags SYN,RST,ACK SYN,ACK' "${match[@]}" ${overwrite_in[@]} proto tcp state INVALID,UNTRACKED "${log[@]}" action ACCEPT nosoftwarnings outface any physout any "${owner[@]}" || return 1
|
|
|
|
# Once the client receives the ACK from SYNPROXY, it will send an ACK back.
|
|
# This ACK will be routed again to SYNPROXY (it will be UNTRACKED too).
|
|
# Once SYNPROXY receives this ACK packet from the client, it will send a SYN to the real server.
|
|
# This SYN packet will be sent via OUTPUT chain. If it goes to localhost, it will be routed via device lo.
|
|
|
|
set_work_function "SYNPROXY->SERVER marking SYN packet at mangle.OUTPUT"
|
|
test ${FIREHOL_SYNPROXY_LOG} -eq 1 && log=(loglimit "SYNPROXY->SERVER SYN MARK")
|
|
rule table mangle chain OUTPUT "${match[@]}" proto tcp custom '-m tcp --syn' state NEW "${log[@]}" action MARK to ${synproxy_mark} nosoftwarnings inface any physin any ${overwrite_out[@]} "${owner[@]}" || return 1
|
|
|
|
case "${action}" in
|
|
DNAT)
|
|
# DNAT
|
|
# Practically we use 'dst' and a possibly defined 'dport' to make the DNAT on the OUTPUT.
|
|
|
|
# If we don't accept the traffic here, after the DNAT this traffic will use interface (not router) rules to reach the destination servers !
|
|
# src and sport will only survive this DNAT, making it impossible to match it.
|
|
# So, we mark the packet before the DNAT (matching dst and original dport) and accept it after the DNAT.
|
|
|
|
set_work_function "SYNPROXY->SERVER DNATing SYN packet at nat.OUTPUT"
|
|
test ${FIREHOL_SYNPROXY_LOG} -eq 1 && log=(loglimit "SYNPROXY->SERVER DNAT")
|
|
dnat "${action_args[@]}" at SYNPROXY2SERVER_OUT "${match[@]}" proto tcp nosoftwarnings "${log[@]}" inface any physin any || return 1
|
|
|
|
for x in ${!FIREHOL_LAST_NAT_MAP[@]}
|
|
do
|
|
local ip=${x/:*/}
|
|
test ! -z "${ip}" && ip="dst ${ip}"
|
|
|
|
local port=${x/*:/}
|
|
test "${port}" = "${x}" && port=
|
|
test ! -z "${port}" && port="dport ${port}"
|
|
|
|
set_work_function "SYNPROXY->SERVER accepting DNAT'd SYN packet at filter.OUTPUT"
|
|
test ${FIREHOL_SYNPROXY_LOG} -eq 1 && log=(loglimit "SYNPROXY->SERVER DNAT'd OUT")
|
|
rule table filter chain SYNPROXY2SERVER_OUT "${match[@]}" proto tcp "${log[@]}" action ACCEPT nosoftwarnings inface any physin any ${ip} ${port} || return 1
|
|
|
|
set_work_function "SERVER->CLIENT dropping INVALID ACK"
|
|
test ${FIREHOL_SYNPROXY_LOG} -eq 1 && log=(loglimit "SERVER->CLIENT INVALID ACK")
|
|
rule table filter chain FORWARD reverse "${match[@]}" custom '-m tcp --tcp-flags RST,ACK ACK' proto tcp state INVALID "${log[@]}" nosoftwarnings ${ip} ${port} action DROP || return 1
|
|
done
|
|
;;
|
|
|
|
REDIRECT)
|
|
# REDIRECT
|
|
|
|
set_work_function "SYNPROXY->SERVER REDIRECTing packet at nat.OUTPUT"
|
|
test ${FIREHOL_SYNPROXY_LOG} -eq 1 && log=(loglimit "SYNPROXY->SERVER REDIRECT")
|
|
redirect "${action_args[@]}" at SYNPROXY2SERVER_OUT "${match[@]}" proto tcp nosoftwarnings "${log[@]}" inface any physin any outface lo physout any || return 1
|
|
|
|
local localhost=
|
|
running_ipv4 && localhost="127.0.0.0/8"
|
|
running_ipv6 && localhost="::1"
|
|
|
|
set_work_function "SYNPROXY->SERVER accepting redirected packet at device lo at filter.OUTPUT"
|
|
test ${FIREHOL_SYNPROXY_LOG} -eq 1 && log=(loglimit "SYNPROXY->SERVER lo OUT")
|
|
rule table filter chain SYNPROXY2SERVER_OUT "${match[@]}" proto tcp "${log[@]}" action ACCEPT nosoftwarnings inface any physin any outface lo physout any dst "${localhost}" dport "${!FIREHOL_LAST_NAT_MAP[*]}" || return 1
|
|
|
|
set_work_function "SYNPROXY->SERVER accepting re-routed packet at device lo at filter.INPUT"
|
|
test ${FIREHOL_SYNPROXY_LOG} -eq 1 && log=(loglimit "SYNPROXY->SERVER lo IN")
|
|
rule table filter chain SYNPROXY2SERVER_IN "${match[@]}" proto tcp "${log[@]}" action ACCEPT nosoftwarnings inface lo physin any outface any physout any dst "${localhost}" dport "${!FIREHOL_LAST_NAT_MAP[*]}" || return 1
|
|
|
|
set_work_function "SERVER->CLIENT dropping INVALID ACK"
|
|
test ${FIREHOL_SYNPROXY_LOG} -eq 1 && log=(loglimit "SERVER->CLIENT INVALID ACK")
|
|
rule table filter chain OUTPUT reverse "${match[@]}" custom '-m tcp --tcp-flags RST,ACK ACK' proto tcp state INVALID "${log[@]}" nosoftwarnings action DROP outface any physout any dport "${!FIREHOL_LAST_NAT_MAP[*]}" || return 1
|
|
|
|
# this requires routing device lo
|
|
set_work_function "SYNPROXY->SERVER enabling routing ${inface[@]} <-> lo"
|
|
setup_lo_for_synproxy "${inface[@]}" || return 1
|
|
;;
|
|
|
|
*)
|
|
# Any other action
|
|
# we allow an action to be defined, since this traffic is now in device lo !
|
|
# Practically we use 'dst' and a possibly defined 'dport' to take the action on OUTPUT.
|
|
|
|
# FIXME
|
|
# we have to check that the action exists in mangle
|
|
|
|
if [ "${chain}" = "INPUT" ]
|
|
then
|
|
set_work_function "SYNPROXY->SERVER executing action ACCEPT at filter.OUTPUT (the packet will come back - re-routed via lo)"
|
|
test ${FIREHOL_SYNPROXY_LOG} -eq 1 && log=(loglimit "SYNPROXY->SERVER ACCEPT OUT (the packet will come back - re-routed via lo)")
|
|
rule table filter chain SYNPROXY2SERVER_OUT "${match[@]}" proto tcp "${log[@]}" nosoftwarnings action ACCEPT inface any physin any outface lo physout any || return 1
|
|
|
|
set_work_function "SYNPROXY->SERVER executing action ${action} after re-route at filter.INPUT"
|
|
test ${FIREHOL_SYNPROXY_LOG} -eq 1 && log=(loglimit "SYNPROXY->SERVER ACTION ${action^^} IN")
|
|
rule table filter chain SYNPROXY2SERVER_IN "${match[@]}" proto tcp "${log[@]}" nosoftwarnings action "${action}" "${action_args[@]}" inface lo physin any outface any physout any || return 1
|
|
|
|
set_work_function "SERVER->CLIENT dropping INVALID ACK"
|
|
test ${FIREHOL_SYNPROXY_LOG} -eq 1 && log=(loglimit "SERVER->CLIENT INVALID ACK")
|
|
rule table filter chain OUTPUT reverse "${match[@]}" custom '-m tcp --tcp-flags RST,ACK ACK' proto tcp state INVALID "${log[@]}" nosoftwarnings action DROP outface any physout any || return 1
|
|
|
|
# this requires routing device lo
|
|
set_work_function "SYNPROXY->SERVER enabling routing ${inface[@]} <-> lo"
|
|
setup_lo_for_synproxy "${inface[@]}" || return 1
|
|
else
|
|
set_work_function "SYNPROXY->SERVER executing action ${action} at filter.OUTPUT (the packet should leave the machine)"
|
|
test ${FIREHOL_SYNPROXY_LOG} -eq 1 && log=(loglimit "SYNPROXY->SERVER ACTION ${action^^} OUT")
|
|
rule table filter chain SYNPROXY2SERVER_OUT "${match[@]}" proto tcp "${log[@]}" nosoftwarnings action "${action}" "${action_args[@]}" inface any physin any || return 1
|
|
|
|
set_work_function "SERVER->CLIENT dropping INVALID ACK"
|
|
test ${FIREHOL_SYNPROXY_LOG} -eq 1 && log=(loglimit "SERVER->CLIENT INVALID ACK")
|
|
rule table filter chain FORWARD reverse "${match[@]}" custom '-m tcp --tcp-flags RST,ACK ACK' proto tcp state INVALID "${log[@]}" nosoftwarnings action DROP || return 1
|
|
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
FIREHOL_TCP_SYN_COOKIES=1
|
|
FIREHOL_TCP_TIMESTAMPS=1
|
|
FIREHOL_DROP_INVALID=1
|
|
FIREHOL_CONNTRACK_LOOSE_MATCHING=0
|
|
}
|
|
|
|
FIREHOL_CTHELPER_WARNING=0
|
|
cthelper4() { ipv4 cthelper "${@}"; }
|
|
cthelper6() { ipv6 cthelper "${@}"; }
|
|
cthelper46() { both cthelper "${@}"; }
|
|
cthelper() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
local helper="${1}" where="${2}"
|
|
shift 2
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
require_work clear || ( error "${FUNCNAME} cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
|
|
|
|
if [ ! "${FIREHOL_CONNTRACK_HELPERS_ASSIGNMENT}" = "manual" -a ${FIREHOL_CTHELPER_WARNING} -eq 0 ]
|
|
then
|
|
warning "Automatic helper assignment on all traffic is set to '${FIREHOL_CONNTRACK_HELPERS_ASSIGNMENT}'. You should set FIREHOL_CONNTRACK_HELPERS_ASSIGNMENT='manual' to disable it since you are using cthelper to configure the helpers."
|
|
FIREHOL_CTHELPER_WARNING=1
|
|
fi
|
|
|
|
case "${helper}" in
|
|
amanda) ;;
|
|
|
|
ftp) ;;
|
|
|
|
tftp) error "${FUNCNAME}: helper '${helper}' cannot be configured"
|
|
;;
|
|
|
|
h323) error "${FUNCNAME}: H.323 cannot be configured."
|
|
return 1
|
|
;;
|
|
|
|
irc) if running_ipv6
|
|
then
|
|
error "${FUNCNAME}: helper '${helper}' does not support IPv6."
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
netbios_ns|netbios-ns|samba)
|
|
helper="netbions_ns"
|
|
error "${FUNCNAME}: helper '${helper}' cannot be configured"
|
|
;;
|
|
|
|
pptp) if running_ipv6
|
|
then
|
|
error "${FUNCNAME}: helper '${helper}' does not support IPv6."
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
proto_gre|gre)
|
|
error "${FUNCNAME}: helper '${helper}' cannot be configured"
|
|
;;
|
|
|
|
sane) ;;
|
|
|
|
sip) # Should include a 'dst' towards the media servers
|
|
# https://home.regit.org/netfilter-en/secure-use-of-helpers/
|
|
;;
|
|
|
|
*) error "${FUNCNAME}: Unknown connection tracker helper '${helper}'."
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
case "${where^^}" in
|
|
IN|INPUT|PREROUTING)
|
|
rule table raw chain PREROUTING "${@}" action CT helper "${helper}" || return 1
|
|
;;
|
|
|
|
OUT|OUTPUT)
|
|
rule table raw chain OUTPUT "${@}" action CT helper "${helper}" || return 1
|
|
;;
|
|
|
|
BOTH|BIDIRECTIONAL|INOUT)
|
|
rule table raw chain PREROUTING "${@}" action CT helper "${helper}" || return 1
|
|
rule table raw chain OUTPUT reverse "${@}" action CT helper "${helper}" || return 1
|
|
;;
|
|
|
|
*)
|
|
rule table raw chain "${where}" "${@}" action CT helper "${helper}" || return 1
|
|
;;
|
|
esac
|
|
|
|
return 0
|
|
}
|
|
|
|
ecn_shame() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
softwarning "ECN_SHAME IP list no longer available, helper is ignored."
|
|
return 0
|
|
}
|
|
|
|
# define custom actions
|
|
action4() { ipv4 action "${@}"; }
|
|
action6() { ipv6 action "${@}"; }
|
|
action46() { both action "${@}"; }
|
|
action() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
require_work clear || ( error "${FUNCNAME} cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
|
|
|
|
local name="${1}" type= tables="filter" t=
|
|
local -a args=()
|
|
shift
|
|
|
|
if [ "${1}" = "table" -o "${1}" = "tables" ]
|
|
then
|
|
tables="${2}"
|
|
shift 2
|
|
fi
|
|
|
|
for t in ${tables/,/ }
|
|
do
|
|
create_chain ${t} ${name} || return 1
|
|
done
|
|
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
type="${1}"
|
|
shift
|
|
|
|
args=()
|
|
while [ ! -z "${1}" -a ! "${1}" = "next" ]
|
|
do
|
|
args=( "${args[@]}" "${1}" )
|
|
shift
|
|
done
|
|
[ "${1}" = "next" ] && shift
|
|
|
|
case "${type}" in
|
|
chain|action)
|
|
for t in ${tables//,/ }
|
|
do
|
|
set_work_function "Rules for type ${type} under table ${t}: ${args[@]}"
|
|
rule table ${t} chain "${name}" action "${args[@]}" || return 1
|
|
done
|
|
;;
|
|
|
|
rule)
|
|
for t in ${tables//,/ }
|
|
do
|
|
set_work_function "Rules for type ${type} under table ${t}: ${args[@]}"
|
|
rule table ${t} chain "${name}" "${args[@]}" || return 1
|
|
done
|
|
;;
|
|
|
|
iptrap)
|
|
local ipt1="${args[0]}" ipt2="${args[1]}" ipt3="${args[2]}"
|
|
unset args[0] args[1] args[2]
|
|
for t in ${tables//,/ }
|
|
do
|
|
set_work_function "Rules for type ${type} under table ${t}: ${args[@]}"
|
|
iptrap "${ipt1}" "${ipt2}" "${ipt3}" chain "${name}" table ${t} "${args[@]}" || return 1
|
|
done
|
|
;;
|
|
|
|
ipuntrap)
|
|
local ipt1="${args[0]}" ipt2="${args[1]}"
|
|
unset args[0] args[1]
|
|
for t in ${tables//,/ }
|
|
do
|
|
set_work_function "Rules for type ${type} under table ${t}: ${args[@]}"
|
|
ipuntrap "${ipt1}" "${ipt2}" chain "${name}" table ${t} "${args[@]}" || return 1
|
|
done
|
|
;;
|
|
|
|
sockets_suspects_trap)
|
|
if [ "${#args[@]}" -lt 3 ]
|
|
then
|
|
error "action ${type} requires 3 parameters: suspects_timeout, trap_timeout, valid_connections"
|
|
return 1
|
|
fi
|
|
|
|
local suspects_timeout="${args[0]}" trap_timeout="${args[1]}" connections="${args[2]}"
|
|
unset args[0] args[1] args[2]
|
|
for t in ${tables//,/ }
|
|
do
|
|
set_work_function "Rules for ${name}_sockets iptrap under table ${t}: ${args[@]}"
|
|
create_chain ${t} "${name}_sockets" || return 1
|
|
iptrap ${name}_sockets src,dst,dst ${suspects_timeout} method "hash:ip,port,ip" counters \
|
|
chain "${name}" table ${t} \
|
|
state NEW log "${name} NEW SOCKET" \
|
|
"${args[@]}" || return 1
|
|
|
|
set_work_function "Rules for ${name}_suspects iptrap under table ${t}"
|
|
create_chain ${t} "${name}_suspects" || return 1
|
|
iptrap ${name}_suspects src ${suspects_timeout} counters \
|
|
chain "${name}" table ${t} \
|
|
state NEW log "${name} NEW SUSPECT" \
|
|
ipset ${name}_sockets src,dst,dst no-counters packets 1 || return 1
|
|
|
|
set_work_function "Rules for ${name}_trap iptrap under table ${t}"
|
|
create_chain ${t} "${name}_trap" || return 1
|
|
iptrap ${name}_trap src ${trap_timeout} \
|
|
chain "${name}" table ${t} \
|
|
state NEW log "${name} TRAPPED" \
|
|
ipset ${name}_suspects src no-counters packets-above ${connections} || return 1
|
|
done
|
|
;;
|
|
|
|
|
|
*)
|
|
error "${FUNCNAME}: Unknown action type '${type}'. Format is: ${FUNCNAME} name type type_parameters"
|
|
return 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
masquerade4() { ipv4 masquerade "${@}"; }
|
|
masquerade6() { ipv6 masquerade "${@}"; }
|
|
masquerade46() { both masquerade "${@}"; }
|
|
masquerade() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
local f="${work_outface}" ports= random=
|
|
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1}" in
|
|
reverse)
|
|
f="${work_inface}"
|
|
shift
|
|
;;
|
|
|
|
ports|to-ports|--to-ports)
|
|
ports="to-ports ${2}"
|
|
shift 2
|
|
;;
|
|
|
|
random|--random)
|
|
random="random"
|
|
shift
|
|
;;
|
|
|
|
*)
|
|
test -z "${f}" && f="${1}" && shift
|
|
break
|
|
;;
|
|
esac
|
|
done
|
|
|
|
test -z "${f}" && error "masquerade requires an interface set or as argument" && return 1
|
|
|
|
set_work_function "Masquerade on interface '${f}'"
|
|
|
|
rule noowner table nat chain POSTROUTING "${@}" inface any outface "${f}" state NEW action MASQUERADE ${ports} ${random} || return 1
|
|
|
|
FIREHOL_NAT=1
|
|
FIREHOL_ROUTING=1
|
|
|
|
return 0
|
|
}
|
|
|
|
transparent_proxy_count=0
|
|
transparent_proxy4() { ipv4 transparent_proxy "${@}"; }
|
|
transparent_proxy6() { ipv6 transparent_proxy "${@}"; }
|
|
transparent_proxy46() { both transparent_proxy "${@}"; }
|
|
transparent_proxy() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
require_work clear || ( error "${FUNCNAME} cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
|
|
|
|
local ports="${1}" \
|
|
redirect="${2}" \
|
|
user="${3}"
|
|
shift 3
|
|
|
|
test -z "${redirect}" && error "Proxy listening port is empty" && return 1
|
|
|
|
transparent_proxy_count=$[transparent_proxy_count + 1]
|
|
|
|
set_work_function "Rules for catching routed tcp/${ports} traffic"
|
|
|
|
#create_chain nat "in_trproxy.${transparent_proxy_count}" PREROUTING noowner "${@}" outface any proto tcp sport "${DEFAULT_CLIENT_PORTS}" dport "${ports}" || return 1
|
|
#rule table nat chain "in_trproxy.${transparent_proxy_count}" proto tcp action REDIRECT to-port ${redirect} || return 1
|
|
rule table nat chain PREROUTING noowner "${@}" outface any proto tcp sport "${DEFAULT_CLIENT_PORTS}" dport "${ports}" action REDIRECT to-port ${redirect} || return 1
|
|
|
|
if [ ! -z "${user}" ]
|
|
then
|
|
set_work_function "Rules for catching outgoing tcp/${ports} traffic"
|
|
create_chain nat "out_trproxy.${transparent_proxy_count}" OUTPUT "${@}" uid not "${user}" nosoftwarnings inface any outface any src any proto tcp sport "${LOCAL_CLIENT_PORTS}" dport "${ports}" || return 1
|
|
|
|
# do not catch traffic for localhost servers
|
|
# rule table nat chain "out_trproxy.${transparent_proxy_count}" dst "127.0.0.1" action RETURN || return 1
|
|
if running_ipv4; then
|
|
ipv4 rule table nat chain "out_trproxy.${transparent_proxy_count}" proto tcp dst not "127.0.0.1" action REDIRECT to-port ${redirect} || return 1
|
|
fi
|
|
if running_ipv6; then
|
|
ipv6 rule table nat chain "out_trproxy.${transparent_proxy_count}" proto tcp dst not "::1" action REDIRECT to-port ${redirect} || return 1
|
|
fi
|
|
fi
|
|
|
|
FIREHOL_NAT=1
|
|
FIREHOL_ROUTING=1
|
|
|
|
return 0
|
|
}
|
|
|
|
transparent_squid4() { ipv4 transparent_squid "${@}"; }
|
|
transparent_squid6() { ipv6 transparent_squid "${@}"; }
|
|
transparent_squid46() { both transparent_squid "${@}"; }
|
|
transparent_squid() {
|
|
transparent_proxy 80 "${@}"
|
|
}
|
|
|
|
FIREHOL_TPROXY_MARK=
|
|
FIREHOL_TPROXY_IP_ROUTE_TABLE="241"
|
|
FIREHOL_TPROXY_ROUTE_DEVICE="lo"
|
|
|
|
tproxy_setup_ip_route() {
|
|
common_require_cmd $PROGRAM_FILE IP_CMD
|
|
|
|
local x=
|
|
for x in inet inet6
|
|
do
|
|
# remove the existing ip rules for this mark
|
|
postprocess -ne ${IP_CMD} -f $x rule del lookup $FIREHOL_TPROXY_IP_ROUTE_TABLE
|
|
|
|
# remove the existing rules from the ip route table
|
|
postprocess -ne ${IP_CMD} -f $x route flush table $FIREHOL_TPROXY_IP_ROUTE_TABLE
|
|
|
|
# add the ip rule to match the mark and forward it to the proper ip route table for tproxy
|
|
postprocess -warn ${IP_CMD} -f $x rule add from all fwmark $FIREHOL_TPROXY_MARK lookup $FIREHOL_TPROXY_IP_ROUTE_TABLE
|
|
|
|
# add the route to forward all traffic to lo, on the ip route table for tproxy
|
|
postprocess -warn ${IP_CMD} -f $x route add local default dev $FIREHOL_TPROXY_ROUTE_DEVICE table $FIREHOL_TPROXY_IP_ROUTE_TABLE
|
|
done
|
|
|
|
# disable the reverse path discovery for lo
|
|
postprocess -warn ${SYSCTL_CMD} -w net.ipv4.conf.default.rp_filter=0
|
|
postprocess -warn ${SYSCTL_CMD} -w net.ipv4.conf.all.rp_filter=0
|
|
postprocess -warn ${SYSCTL_CMD} -w net.ipv4.conf.$FIREHOL_TPROXY_ROUTE_DEVICE.rp_filter=0
|
|
}
|
|
|
|
tproxy_count=0
|
|
|
|
tproxy4() { ipv4 tproxy "${@}"; }
|
|
tproxy6() { ipv6 tproxy "${@}"; }
|
|
tproxy46() { both tproxy "${@}"; }
|
|
tproxy() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
require_work clear || ( error "${FUNCNAME} cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
|
|
|
|
local ports="${1}"; shift
|
|
|
|
if [ -z "${FIREHOL_TPROXY_MARK}" ]
|
|
then
|
|
markdef tproxymark 2 temporary stateless || return 1
|
|
FIREHOL_TPROXY_MARK="$(mark_value tproxymark 1)"
|
|
fi
|
|
|
|
local tproxy_action_options="tproxy-mark $FIREHOL_TPROXY_MARK" \
|
|
tport= \
|
|
tip=
|
|
|
|
if [ "$1" = "port" ]
|
|
then
|
|
tproxy_action_options="$tproxy_action_options on-port ${2}"
|
|
tport="${2}"
|
|
shift 2
|
|
else
|
|
error "TPROXY needs at least the port the proxy is listening at."
|
|
return 1
|
|
fi
|
|
|
|
if [ "$1" = "ip" ]
|
|
then
|
|
tproxy_action_options="$tproxy_action_options on-ip ${2}"
|
|
tip="${2}"
|
|
shift 2
|
|
fi
|
|
|
|
tproxy_count=$[tproxy_count + 1]
|
|
|
|
set_work_function "Rules for catching routed tcp/${ports} traffic"
|
|
|
|
create_chain mangle "in_tproxy.${tproxy_count}" PREROUTING "${@}" outface any proto tcp dport "${ports}" || return 1
|
|
|
|
create_chain mangle "in_tproxy.${tproxy_count}.divert" "in_tproxy.${tproxy_count}" proto tcp custom '-m socket' || return 1
|
|
rule table mangle chain "in_tproxy.${tproxy_count}.divert" action MARK to $FIREHOL_TPROXY_MARK
|
|
rule table mangle chain "in_tproxy.${tproxy_count}.divert" action ACCEPT
|
|
|
|
rule table mangle chain "in_tproxy.${tproxy_count}" proto tcp action TPROXY ${tproxy_action_options} || return 1
|
|
|
|
FIREHOL_NAT=1
|
|
FIREHOL_ROUTING=1
|
|
|
|
if [ $tproxy_count -eq 1 ]
|
|
then
|
|
tproxy_setup_ip_route
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
declare -A FIREHOL_LAST_NAT_MAP=()
|
|
nat_count=0
|
|
nat_helper() {
|
|
# work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
require_work clear || ( error "NAT cannot be used in '${work_cmd}'. Put all NAT related commands before any '${work_cmd}' definition."; return 1 )
|
|
|
|
local type="${1}" to="${2}" tos=() name= persistent=0 \
|
|
chain= chains=() action= options= requirements= overwrite= \
|
|
id= x= w= total= balance=() args=() proto=(any) total_weight=0 without_weight=0 mode="nth" do_recent=0 \
|
|
nat_log=()
|
|
shift 2
|
|
|
|
nat_count=$[nat_count + 1]
|
|
FIREHOL_LAST_NAT_MAP=()
|
|
|
|
set_work_function -ne "Rules for NAT type: '${type}'"
|
|
case ${type} in
|
|
to-source)
|
|
chains=(POSTROUTING)
|
|
#possible_chains="POSTROUTING INPUT"
|
|
action=snat
|
|
overwrite="inface any"
|
|
tos=(${to//,/ })
|
|
;;
|
|
|
|
to-destination)
|
|
chains=(PREROUTING)
|
|
#possible_chains="PREROUTING OUTPUT"
|
|
action=dnat
|
|
requirements=noowner
|
|
overwrite="outface any"
|
|
tos=(${to//,/ })
|
|
;;
|
|
|
|
redirect-to)
|
|
chains=(PREROUTING)
|
|
#possible_chains="PREROUTING OUTPUT"
|
|
action=redirect
|
|
requirements=noowner
|
|
overwrite="outface any"
|
|
tos=(${to//,/ })
|
|
;;
|
|
|
|
*)
|
|
error "${FUNCNAME} requires a type (i.e. to-source, to-destination, redirect-to, etc) as its first argument. '${type}' is not understood."
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
args=(${requirements})
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1,,}" in
|
|
id|--id)
|
|
id="${2}"
|
|
shift
|
|
;;
|
|
|
|
random|--random)
|
|
options="${options} random"
|
|
;;
|
|
|
|
persistent|--persistent)
|
|
persistent=1
|
|
options="${options} persistent"
|
|
[ "${action}" = "redirect" ] && error "Persistence is not supported by redirect." && return 1
|
|
;;
|
|
|
|
at) chains=()
|
|
for chain in ${2//,/ }
|
|
do
|
|
case "${chain^^}" in
|
|
PRE|PREROUTING) chains=("${chains[@]}" "PREROUTING") ;;
|
|
IN|INPUT) chains=("${chains[@]}" "INPUT") ;;
|
|
OUT|OUTPUT) chains=("${chains[@]}" "OUTPUT") ;;
|
|
PASS|FORWARD) chains=("${chains[@]}" "FORWARD") ;;
|
|
POST|POSTROUTING) chains=("${chains[@]}" "POSTROUTING") ;;
|
|
*) chains=("${chains[@]}" "${chain}");;
|
|
esac
|
|
done
|
|
shift
|
|
;;
|
|
|
|
name) name="${2}"
|
|
shift
|
|
;;
|
|
|
|
# we need the protocol in case of a balancer
|
|
proto) proto=(${2//,/ })
|
|
shift
|
|
;;
|
|
|
|
*) args=("${args[@]}" "${1}")
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
args=("${args[@]}" ${overwrite} nosoftwarnings state NEW)
|
|
|
|
# the total combinations we have
|
|
total="${#tos[@]}"
|
|
|
|
# If a name is given, or a balancer is requested (multiple 'to' value) or multiple chains
|
|
# are requested, centralize it on one chain, so that the balancing rules will be applied
|
|
# just once (this will ensure proper balancing)
|
|
if [ ! -z "${name}" -o ${total} -gt 1 -o ${#chains[@]} -gt 1 ]
|
|
then
|
|
# If a balancer is requested on multiple chains centralize it on one chain,
|
|
# so that the balancing rules will be applied just once (this will ensure proper balancing)
|
|
|
|
test -z "${name}" && name="BALANCER.${nat_count}"
|
|
|
|
# create the chain
|
|
create_chain nat "${name}" || return 1
|
|
|
|
# link it to the places requested
|
|
for chain in ${chains[@]}
|
|
do
|
|
set_work_function "Linking chain ${name} at ${chain}"
|
|
rule table nat chain ${chain} proto "${proto[*]}" "${args[@]}" action "${name}" || return 1
|
|
done
|
|
|
|
# change the linking for the balancing rules
|
|
chains=("${name}")
|
|
|
|
# and empty the parameters (traffic on the chain BALANCER.x is already this traffic)
|
|
args=()
|
|
fi
|
|
|
|
# check if all 'to' have weights
|
|
total_weight=0
|
|
without_weight=0
|
|
for to in ${tos[@]}
|
|
do
|
|
x=${to/*\//}
|
|
[ "${x}" = "${to}" ] && x=
|
|
if [ -z "${x}" ]
|
|
then
|
|
(( without_weight += 1 ))
|
|
else
|
|
total_weight=$[total_weight + x]
|
|
fi
|
|
done
|
|
|
|
# if some have weight and some don't
|
|
# stop with an error
|
|
if [ ${total_weight} -ne 0 ]
|
|
then
|
|
if [ ${without_weight} -gt 0 ]
|
|
then
|
|
error "Weights based balancing is requested, but there are ${without_weight} value(s) that do not have a weight."
|
|
return 1
|
|
fi
|
|
mode="random"
|
|
else
|
|
mode="nth"
|
|
fi
|
|
|
|
# check if we have to enable the use of the recent module
|
|
# to track persistence, ourselves
|
|
if [ ${total} -gt 1 -a ${persistent} -eq 1 ]
|
|
then
|
|
if [ -z "${id}" ]
|
|
then
|
|
warning "You have not specified an ID for ${action} with multiple servers and persistence. Using 'id ${id}', but you should give one yourself to allow persistence to work properly between firewall restarts."
|
|
fi
|
|
do_recent=1
|
|
fi
|
|
|
|
# make sure there is an id always
|
|
[ -z "${id}" ] && id="${name-BALANCER.${nat_count}}"
|
|
|
|
# if our persistence is enabled, make a chain for updating it
|
|
# to take the final action
|
|
if [ ${do_recent} -eq 1 ]
|
|
then
|
|
set_work_function "Matching connections for persistence..."
|
|
|
|
for to in ${tos[@]}
|
|
do
|
|
local rname="$( echo "${id}.${to/\/*}" | $TR_CMD ".\-: " "____" )"
|
|
[ ${FIREHOL_NAT_HELPER_LOG} -eq 1 ] && nat_log=(log "NAT_HELPER ${id}: UPDATED PERSISTENCE FOR ${to/\/*}")
|
|
|
|
set_work_function "Creating chain '${rname}' to update persistence for '${to/\/*}'..."
|
|
|
|
create_chain nat "${rname}"
|
|
rule table nat chain "${rname}" custom "-m recent --set --name ${rname} --rsource" action NOOP
|
|
rule table nat chain "${rname}" custom "-m recent --update --name ${rname} --rsource --seconds ${FIREHOL_NAT_PERSISTENCE_SECONDS} --reap" action NOOP
|
|
rule table nat chain "${rname}" proto "${proto[*]}" "${nat_log[@]}" action "${action}" to ${to/\/*} ${options}
|
|
|
|
[ ${FIREHOL_NAT_HELPER_LOG} -eq 1 ] && nat_log=(log "NAT_HELPER ${id} PERSISTED PACKET FOR ${to/\/*}")
|
|
for chain in ${chains[@]}
|
|
do
|
|
set_work_function "Matching persisted connections for '${to/\/*}' at ${chain}..."
|
|
rule table nat chain "${chain}" proto "${proto[*]}" "${args[@]}" custom "-m recent --rcheck --name ${rname} --rsource" "${nat_log[@]}" action "${rname}" || return 1
|
|
done
|
|
done
|
|
fi
|
|
|
|
x=0
|
|
for to in ${tos[@]}
|
|
do
|
|
balance=()
|
|
|
|
# if we build a balancer, generate the rule to split the traffic among
|
|
# the target servers, except if this is the last host
|
|
if [ ${total} -gt 1 -a $[x+1] -lt ${total} ]
|
|
then
|
|
if [ "${mode}" = "nth" ]
|
|
then
|
|
# round robin mode
|
|
balance=("custom" "-m statistic --mode nth --every ${total} --packet ${x}")
|
|
else
|
|
# weighted random mode
|
|
w=${to/*\//}
|
|
if [ "${w}" = "${to}" -o -z "${w}" ]
|
|
then
|
|
error "Cannot parse weight from ip ${ip}."
|
|
return 1
|
|
fi
|
|
w=$[(w * 1000) / total_weight]
|
|
if [ ${w} -lt 10 ]
|
|
then
|
|
w="0.00${w}"
|
|
elif [ ${w} -lt 100 ]
|
|
then
|
|
w="0.0${w}"
|
|
elif [ ${w} -lt 1000 ]
|
|
then
|
|
w="0.${w}"
|
|
elif [ ${w} -eq 1000 ]
|
|
then
|
|
w="1.0"
|
|
else
|
|
error "Cannot calculate weight for ${to}. Calculation gives ${w} / 1000."
|
|
return 1
|
|
fi
|
|
|
|
balance=("custom" "-m statistic --mode random --probability ${w}")
|
|
fi
|
|
fi
|
|
(( x += 1 ))
|
|
|
|
to=${to/\/*/}
|
|
FIREHOL_LAST_NAT_MAP[$to]="${balance[*]}"
|
|
[ ${FIREHOL_NAT_HELPER_LOG} -eq 1 ] && nat_log=(log "NAT_HELPER ${id} NEW PACKET FOR ${to/\/*}")
|
|
for chain in ${chains[@]}
|
|
do
|
|
local laction=("${action}" to ${to} ${options})
|
|
|
|
if [ ${do_recent} -eq 1 ]
|
|
then
|
|
local rname="$( echo "${id}.${to/\/*}" | $TR_CMD ".\-: " "____" )"
|
|
laction=("${rname}")
|
|
fi
|
|
|
|
set_work_function "Rules for ${action} to ${to} at ${chain}"
|
|
rule table nat chain "${chain}" proto "${proto[*]}" "${args[@]}" "${balance[@]}" "${nat_log[@]}" action "${laction[@]}" || return 1
|
|
done
|
|
done
|
|
|
|
FIREHOL_NAT=1
|
|
[ ! "${action}" = "redirect" ] && FIREHOL_ROUTING=1
|
|
|
|
return 0
|
|
}
|
|
|
|
nat4() { ipv4 nat "${@}"; }
|
|
nat6() { ipv6 nat "${@}"; }
|
|
nat46() { both nat "${@}"; }
|
|
nat() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
nat_helper "${@}"
|
|
}
|
|
|
|
snat4() { ipv4 snat "${@}"; }
|
|
snat6() { ipv6 snat "${@}"; }
|
|
snat46() { both snat "${@}"; }
|
|
snat() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
local to="${1}"; shift
|
|
test "${to}" = "to" && to="${1}" && shift
|
|
|
|
nat_helper "to-source" "${to}" "${@}"
|
|
}
|
|
|
|
dnat4() { ipv4 dnat "${@}"; }
|
|
dnat6() { ipv6 dnat "${@}"; }
|
|
dnat46() { both dnat "${@}"; }
|
|
dnat() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
local to="${1}"; shift
|
|
test "${to}" = "to" && to="${1}" && shift
|
|
|
|
nat_helper "to-destination" "${to}" "${@}"
|
|
}
|
|
|
|
redirect4() { ipv4 redirect "${@}"; }
|
|
redirect6() { ipv6 redirect "${@}"; }
|
|
redirect46() { both redirect "${@}"; }
|
|
redirect() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
local to="${1}"; shift
|
|
test "${to}" = "to" -o "${to}" = "to-port" && to="${1}" && shift
|
|
|
|
nat_helper "redirect-to" "${to}" "${@}"
|
|
}
|
|
|
|
wrongmac_chain=0
|
|
wrongmac6_chain=0
|
|
mac4() { ipv4 mac "${@}"; }
|
|
mac6() { ipv6 mac "${@}"; }
|
|
mac46() { both mac "${@}"; }
|
|
mac() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
require_work clear || ( error "${FUNCNAME} cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
|
|
|
|
if running_ipv4; then
|
|
if [ ${wrongmac_chain} -eq 0 ]
|
|
then
|
|
set_work_function "Creating the MAC-MISSMATCH chain (only once)"
|
|
|
|
iptables -t filter -N WRONGMAC
|
|
rule table filter chain WRONGMAC loglimit "BLOCKED MAC MISSMATCH" action DROP || return 1
|
|
|
|
wrongmac_chain=1
|
|
fi
|
|
fi
|
|
|
|
if running_ipv6; then
|
|
if [ ${wrongmac6_chain} -eq 0 ]
|
|
then
|
|
set_work_function "Creating the MAC-MISSMATCH chain (only once)"
|
|
|
|
ip6tables -t filter -N WRONGMAC
|
|
rule table filter chain WRONGMAC loglimit "BLOCKED MAC MISSMATCH" action DROP || return 1
|
|
|
|
wrongmac6_chain=1
|
|
fi
|
|
fi
|
|
|
|
set_work_function "Source IP ${1} does not match MAC ${2}"
|
|
|
|
iptables_both -t filter -A INPUT -s "${1}" -m mac ! --mac-source "${2}" -j WRONGMAC
|
|
iptables_both -t filter -A FORWARD -s "${1}" -m mac ! --mac-source "${2}" -j WRONGMAC
|
|
|
|
return 0
|
|
}
|
|
|
|
log() { log_helper log "${@}"; }
|
|
log4() { ipv4 log_helper log "${@}"; }
|
|
log6() { ipv6 log_helper log "${@}"; }
|
|
log46() { ipv46 log_helper log "${@}"; }
|
|
loglimit() { log_helper loglimit "${@}"; }
|
|
loglimit4() { ipv4 log_helper loglimit "${@}"; }
|
|
loglimit6() { ipv6 log_helper loglimit "${@}"; }
|
|
loglimit46() { ipv46 log_helper loglimit "${@}"; }
|
|
log_helper() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
require_work clear || ( error "${FUNCNAME} cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
|
|
|
|
local log="${1}" msg="${2}" x=
|
|
shift 2
|
|
|
|
for x in INPUT FORWARD
|
|
do
|
|
set_work_function "requests log on ${x}"
|
|
rule table filter chain ${x} in ${log} "${msg}-IN" "${@}" action NONE
|
|
done
|
|
|
|
for x in FORWARD OUTPUT
|
|
do
|
|
set_work_function "replies log on ${x}"
|
|
rule out reverse table filter chain ${x} ${log} "${msg}-OUT" "${@}" action NONE
|
|
done
|
|
}
|
|
|
|
# blacklist creates two types of blacklists: unidirectional or bidirectional
|
|
FIREHOL_BLACKLIST_COUNTER=0
|
|
blacklist4() { ipv4 blacklist "${@}"; }
|
|
blacklist6() { ipv6 blacklist "${@}"; }
|
|
blacklist46() { both blacklist "${@}"; }
|
|
blacklist() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
require_work clear || ( error "${FUNCNAME} cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
|
|
|
|
FIREHOL_BLACKLIST_COUNTER=$[ FIREHOL_BLACKLIST_COUNTER + 1 ]
|
|
local mode=1 chain= name="bi" accounting= x=
|
|
local -a inface=() src=() logopts_in_arg=(log "BLACKLIST-IN") logopts_out_arg=(log "BLACKLIST-OUT")
|
|
|
|
case "${1}" in
|
|
them|him|her|it|this|these|input)
|
|
mode=0
|
|
name="uni"
|
|
shift
|
|
;;
|
|
|
|
all|full)
|
|
mode=1
|
|
name="bi"
|
|
shift
|
|
;;
|
|
|
|
stateful|statefull)
|
|
mode=2
|
|
name="bs"
|
|
shift
|
|
;;
|
|
|
|
*) ;;
|
|
esac
|
|
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1,,}" in
|
|
src)
|
|
shift
|
|
;;
|
|
|
|
except)
|
|
shift
|
|
break
|
|
;;
|
|
|
|
nolog)
|
|
logopts_in_arg=()
|
|
logopts_out_arg=()
|
|
shift
|
|
;;
|
|
|
|
log)
|
|
logopts_in_arg=(log "${2}-IN")
|
|
logopts_out_arg=(log "${2}-OUT")
|
|
shift 2
|
|
;;
|
|
|
|
connlog)
|
|
logopts_in_arg=(connlog "${2}-IN")
|
|
logopts_out_arg=(connlog "${2}-OUT")
|
|
shift 2
|
|
;;
|
|
|
|
loglimit)
|
|
logopts_in_arg=(loglimit "${2}-IN")
|
|
logopts_out_arg=(loglimit "${2}-OUT")
|
|
shift 2
|
|
;;
|
|
|
|
acct|accounting)
|
|
accounting="${2}"
|
|
shift 2
|
|
;;
|
|
|
|
inface)
|
|
if [ "${2}" = "not" ]
|
|
then
|
|
inface=(inface not "${3}")
|
|
shift 3
|
|
else
|
|
inface=(inface "${2}")
|
|
shift 2
|
|
fi
|
|
;;
|
|
|
|
*) src=( "${src[@]}" "${1}" )
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Now in $src[@] we have all the positive IPs
|
|
# and in $@ the excepted rules
|
|
|
|
|
|
chain="BLACKLIST.${name}.${FIREHOL_BLACKLIST_COUNTER}"
|
|
set_work_function "Blacklist input chain"
|
|
|
|
# create the input chain (common for both stateless and stateful)
|
|
iptables_both -t filter -N "${chain}.in"
|
|
|
|
# add the excepted rules
|
|
if [ ! -z "${1}" ]; then rule table filter chain "${chain}.in" in action RETURN "${@}" || return 1; fi
|
|
|
|
# add the accounting rules
|
|
if [ ! -z "${accounting}" ]; then iptables_both -t filter -A "${chain}.in" -m nfacct --nfacct-name "${accounting}" || return 1; fi
|
|
|
|
# drop the traffic (input)
|
|
rule table filter chain "${chain}.in" in "${logopts_in_arg[@]}" action DROP || return 1
|
|
|
|
# ---
|
|
|
|
# send traffic from the main flow to the generated chains
|
|
if [ ${mode} -eq 0 ]
|
|
then
|
|
# uni-directional stateful
|
|
# we will be able to connect to them, they will not be able to connect to us
|
|
|
|
set_work_function "Unidirectional blacklist rules"
|
|
|
|
for x in INPUT FORWARD
|
|
do
|
|
rule table filter chain ${x} in "${inface[@]}" src "${src[*]}" state NEW action "${chain}.in" || return 1
|
|
done
|
|
|
|
else
|
|
local state=
|
|
|
|
[ ${mode} -eq 2 ] && state="state NEW"
|
|
|
|
# bi-directional stateless
|
|
# no traffic from/to them
|
|
|
|
set_work_function "Blacklist input"
|
|
|
|
for x in INPUT FORWARD
|
|
do
|
|
rule table filter chain ${x} in "${inface[@]}" src "${src[*]}" ${state} action "${chain}.in" || return 1
|
|
done
|
|
|
|
set_work_function "Blacklist output chain"
|
|
|
|
# create the output chain
|
|
iptables_both -t filter -N "${chain}.out"
|
|
|
|
# add the excepted rules
|
|
if [ ! -z "${1}" ]; then rule table filter chain "${chain}.out" out reverse action RETURN "${@}" || return 1; fi
|
|
|
|
# add the accounting rules
|
|
if [ ! -z "${accounting}" ]; then iptables_both -t filter -A "${chain}.out" -m nfacct --nfacct-name "${accounting}" || return 1; fi
|
|
|
|
if running_ipv4
|
|
then
|
|
push_namespace ipv4
|
|
rule table filter chain "${chain}.out" out "${logopts_out_arg[@]}" proto tcp action REJECT with tcp-reset
|
|
rule table filter chain "${chain}.out" out "${logopts_out_arg[@]}" action REJECT with icmp-host-unreachable
|
|
pop_namespace
|
|
fi
|
|
|
|
if running_ipv6
|
|
then
|
|
push_namespace ipv6
|
|
rule table filter chain "${chain}.out" out "${logopts_out_arg[@]}" proto tcp action REJECT with tcp-reset
|
|
rule table filter chain "${chain}.out" out "${logopts_out_arg[@]}" action REJECT with icmp6-addr-unreachable
|
|
pop_namespace
|
|
fi
|
|
|
|
# bi-directional stateless
|
|
# none connects from/to these hosts
|
|
|
|
set_work_function "Bidirectional blacklist rules"
|
|
|
|
# iptables does not accept REJECT on mangle - we are forced to use filter for this
|
|
for x in FORWARD OUTPUT
|
|
do
|
|
rule table filter chain ${x} out reverse "${inface[@]}" src "${src[*]}" ${state} action "${chain}.out" || return 1
|
|
done
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
FIREHOL_IPTRAP_COUNTER=0
|
|
|
|
declare -A FIREHOL_IPTRAP_MODE=()
|
|
declare -A FIREHOL_IPTRAP_METHOD=()
|
|
|
|
ipuntrap4() { ipv4 iptrap undo "${@}"; }
|
|
ipuntrap6() { ipv6 iptrap undo "${@}"; }
|
|
ipuntrap46() { both iptrap undo "${@}"; }
|
|
ipuntrap() { iptrap undo "${@}"; }
|
|
|
|
iptrap4() { ipv4 iptrap "${@}"; }
|
|
iptrap6() { ipv6 iptrap "${@}"; }
|
|
iptrap46() { both iptrap "${@}"; }
|
|
iptrap() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
require_work clear || ( error "${FUNCNAME} cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
|
|
running_both && error "${FUNCNAME} cannot be used in both IPv4 and IPv6. Please give use either iptrap4 or iptrap6." && return 1
|
|
|
|
local type= ipset= timeout= chain= method= tables="filter" undo=0 action="RETURN" t= link_to="INPUT FORWARD" mode="TIMEOUT" x=
|
|
local -a args=() logopts_arg=()
|
|
|
|
if [ "${1}" = "undo" ]
|
|
then
|
|
undo=1
|
|
ipset="${2}"
|
|
type="${3}"
|
|
logopts_arg=(log "UNTRAP")
|
|
else
|
|
ipset="${1}"
|
|
type="${2}"
|
|
timeout="${3}"
|
|
logopts_arg=(log "TRAP")
|
|
fi
|
|
shift 3
|
|
|
|
if [ ${ENABLE_IPSET} -ne 1 ]
|
|
then
|
|
error "${FUNCNAME} requires ipset but ut is not enabled. Do you have ipset installed?"
|
|
return 1
|
|
fi
|
|
|
|
# validate type and generate default ipset storage method
|
|
for x in ${type//,/ }
|
|
do
|
|
case "${x}" in
|
|
src|dst)
|
|
if [ ! -z "${method}" ]
|
|
then
|
|
method="${method},ip"
|
|
else
|
|
method="hash:ip"
|
|
fi
|
|
;;
|
|
*)
|
|
error "${FUNCNAME}: invalid type '${x}'. It can either be 'src' or 'dst'."
|
|
return 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# get the last mode used
|
|
[ ! -z "${FIREHOL_IPTRAP_MODE[$ipset]}" ] && mode="${FIREHOL_IPTRAP_MODE[$ipset]}"
|
|
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1,,}" in
|
|
method)
|
|
method="${2}"
|
|
shift
|
|
;;
|
|
|
|
counters)
|
|
mode="COUNTERS"
|
|
;;
|
|
|
|
timeout)
|
|
mode="TIMEOUT"
|
|
;;
|
|
|
|
at|chain)
|
|
link_to="${2}"
|
|
shift
|
|
;;
|
|
|
|
table|tables)
|
|
tables="${2}"
|
|
shift
|
|
;;
|
|
|
|
action)
|
|
action="${2}"
|
|
shift
|
|
;;
|
|
|
|
except)
|
|
shift
|
|
break
|
|
;;
|
|
|
|
log|loglimit|connlog)
|
|
logopts_arg=("${1}" "${2}")
|
|
shift
|
|
;;
|
|
|
|
*) args=("${args[@]}" "${1}")
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# Now in $args[@] we have all the positive IPs
|
|
# and in $@ the excepted rules
|
|
|
|
# check if the caller changed the update mode of the ipset
|
|
if [ ! -z "${FIREHOL_IPTRAP_MODE[$ipset]}" ]
|
|
then
|
|
if [ ! "${FIREHOL_IPTRAP_MODE[$ipset]}" = "${mode}" ]
|
|
then
|
|
warning "${FUNCNAME}: ipset '${ipset}' was previously used with option ${FIREHOL_IPTRAP_MODE[$ipset]}, while now ${mode} is requested."
|
|
fi
|
|
else
|
|
# remember the mode if this ipset
|
|
FIREHOL_IPTRAP_MODE[$ipset]="${mode}"
|
|
fi
|
|
|
|
# check if the caller changed the storage method of the ipset
|
|
if [ ! -z "${FIREHOL_IPTRAP_METHOD[$ipset]}" ]
|
|
then
|
|
if [ ! "${FIREHOL_IPTRAP_METHOD[$ipset]}" = "${method}" ]
|
|
then
|
|
warning "${FUNCNAME}: ipset '${ipset}' was previously used with storage method ${FIREHOL_IPTRAP_METHOD[$ipset]}, while now ${method} is requested."
|
|
fi
|
|
else
|
|
# remember the mode if this ipset
|
|
FIREHOL_IPTRAP_METHOD[$ipset]="${method}"
|
|
fi
|
|
|
|
# if the ipset has not been created by us, create it
|
|
if [ ! "${FIREHOL_IPSETS_USED[$ipset]}" = "CREATED" ]
|
|
then
|
|
local opts="${IPTRAP_DEFAULT_IPSET_TIMEOUT_OPTIONS}"
|
|
test "${mode}" = "COUNTERS" && opts="${IPTRAP_DEFAULT_IPSET_COUNTERS_OPTIONS}"
|
|
|
|
ipset create ${ipset} ${method} ${opts} prevent_reset_on_restart
|
|
fi
|
|
|
|
FIREHOL_IPTRAP_COUNTER=$[ FIREHOL_IPTRAP_COUNTER + 1 ]
|
|
chain="IPTRAP.${FIREHOL_IPTRAP_COUNTER}"
|
|
|
|
for t in ${tables//,/ }
|
|
do
|
|
set_work_function "Trap chain ${chain} in table ${t}"
|
|
|
|
# create the chain
|
|
iptables_both -t ${t} -N "${chain}"
|
|
|
|
# add the excepted rules
|
|
if [ ! -z "${1}" ]; then rule table ${t} chain "${chain}" in action RETURN "${@}" || return 1; fi
|
|
|
|
# do the job
|
|
if [ ${undo} -eq 1 ]
|
|
then
|
|
# remove the ip
|
|
iptables_both -t ${t} -A "${chain}" -j SET --del-set ${ipset} ${type}
|
|
else
|
|
if [ "${mode}" = "COUNTERS" ]
|
|
then
|
|
# this command updates the counters
|
|
# but its presence mean that the timer is not updated
|
|
iptables_both -t ${t} -A "${chain}" -m set --match-set ${ipset} ${type} -j RETURN || return 1
|
|
fi
|
|
|
|
# add the ip
|
|
if [ -z "${timeout}" -o "${timeout}" = "default" ]
|
|
then
|
|
iptables_both -t ${t} -A "${chain}" -j SET --add-set ${ipset} ${type} --exist || return 1
|
|
else
|
|
iptables_both -t ${t} -A "${chain}" -j SET --add-set ${ipset} ${type} --exist --timeout ${timeout} || return 1
|
|
fi
|
|
fi
|
|
|
|
# log and return
|
|
rule table ${t} chain "${chain}" in "${logopts_arg[@]}" action "${action}" || return 1
|
|
|
|
# ---
|
|
|
|
for x in ${link_to}
|
|
do
|
|
set_work_function "iptrap rules in table ${t} chain ${x}"
|
|
rule table ${t} chain ${x} in "${args[@]}" action "${chain}" || return 1
|
|
done
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
classify_count=0
|
|
classify4() { ipv4 classify "${@}"; }
|
|
classify6() { ipv6 classify "${@}"; }
|
|
classify46() { both classify "${@}"; }
|
|
classify() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
require_work clear || ( error "${FUNCNAME} cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
|
|
|
|
local class="${1}"; shift
|
|
classify_count=$[classify_count + 1]
|
|
|
|
set_work_function "Rules for CLASSIFY"
|
|
|
|
create_chain mangle "classify.${classify_count}" POSTROUTING "${@}" || return 1
|
|
iptables_both -t mangle -A "classify.${classify_count}" -j CLASSIFY --set-class ${class}
|
|
|
|
return 0
|
|
}
|
|
|
|
connmark4() { ipv4 connmark "${@}"; }
|
|
connmark6() { ipv6 connmark "${@}"; }
|
|
connmark46() { both connmark "${@}"; }
|
|
connmark() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
require_work clear || ( error "${FUNCNAME} cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
|
|
|
|
local num="${1}" where="${2}" chain=
|
|
shift 2
|
|
|
|
if [ "${num}" = "save" ]
|
|
then
|
|
# backward compatibility - nothing to be done here
|
|
return 0
|
|
fi
|
|
|
|
if [ "${num}" = "restore" ]
|
|
then
|
|
# backward compatibility - nothing to be done here
|
|
return 0
|
|
fi
|
|
|
|
local mark="$(mark_value connmark $num)"
|
|
test -z "${mark}" && work_error=$[work_error + 1] && return 1
|
|
|
|
test -z "${where}" && where="OUTPUT POSTROUTING"
|
|
for chain in ${where//,/ }
|
|
do
|
|
case "${chain^^}" in
|
|
PRE|PREROUTING) chain="PREROUTING" ;;
|
|
IN|INPUT) chain="INPUT" ;;
|
|
OUT|OUTPUT) chain="OUTPUT" ;;
|
|
PASS|FORWARD) chain="FORWARD" ;;
|
|
POST|POSTROUTING) chain="POSTROUTING" ;;
|
|
esac
|
|
|
|
case "${chain}" in
|
|
interface)
|
|
if [ ${MARKS_STATEFUL[connmark]} -eq 1 ]
|
|
then
|
|
set_work_function "Stateful rules for CONNMARK ${mark} for interface ${1}"
|
|
rule table mangle chain PREROUTING state NEW inface "${@}" action MARK to "${mark}" || return 1
|
|
rule table mangle chain POSTROUTING state NEW outface "${@}" action MARK to "${mark}" || return 1
|
|
else
|
|
set_work_function "Stateless rules for CONNMARK ${mark} for interface ${1}"
|
|
rule table mangle chain PREROUTING inface "${@}" action MARK to "${mark}" || return 1
|
|
rule table mangle chain POSTROUTING outface "${@}" action MARK to "${mark}" || return 1
|
|
fi
|
|
;;
|
|
|
|
*)
|
|
if [ ${MARKS_STATEFUL[connmark]} -eq 1 ]
|
|
then
|
|
set_work_function "Stateful rules for CONNMARK ${mark} on chain ${chain}"
|
|
rule table mangle chain "${chain}" state NEW "${@}" action MARK to "${mark}" || return 1
|
|
else
|
|
set_work_function "Stateless rules for CONNMARK ${mark} on chain ${chain}"
|
|
rule table mangle chain "${chain}" action MARK to "${mark}" || return 1
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
custommark4() { ipv4 custommark "${@}"; }
|
|
custommark6() { ipv6 custommark "${@}"; }
|
|
custommark46() { both custommark "${@}"; }
|
|
custommark() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
require_work clear || ( error "${FUNCNAME} cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
|
|
|
|
local name="${1}" num="${2}" where="${3}" chain=
|
|
shift 3
|
|
|
|
local mark="$(mark_value $name $num)"
|
|
test -z "${mark}" && work_error=$[work_error + 1] && return 1
|
|
|
|
test -z "${where}" && where="OUTPUT"
|
|
for chain in ${where//,/ }
|
|
do
|
|
case "${chain^^}" in
|
|
PRE|PREROUTING) chain="PREROUTING" ;;
|
|
IN|INPUT) chain="INPUT" ;;
|
|
OUT|OUTPUT) chain="OUTPUT" ;;
|
|
PASS|FORWARD) chain="FORWARD" ;;
|
|
POST|POSTROUTING) chain="POSTROUTING" ;;
|
|
esac
|
|
|
|
if [ ${MARKS_STATEFUL[$name]} -eq 1 ]
|
|
then
|
|
set_work_function "Rules for stateful MARK ${mark} on chain ${chain}"
|
|
rule table mangle chain "${chain}" state NEW "${@}" action MARK to "${mark}" || return 1
|
|
else
|
|
set_work_function "Rules for stateless MARK ${mark} on chain ${chain}"
|
|
rule table mangle chain "${chain}" "${@}" action MARK to "${mark}" || return 1
|
|
fi
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
mark4() { ipv4 mark "${@}"; }
|
|
mark6() { ipv6 mark "${@}"; }
|
|
mark46() { both mark "${@}"; }
|
|
mark() {
|
|
custommark usermark "${@}"
|
|
}
|
|
|
|
tos4() { ipv4 tos "${@}"; }
|
|
tos6() { ipv6 tos "${@}"; }
|
|
tos46() { both tos "${@}"; }
|
|
tos() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
require_work clear || ( error "${FUNCNAME} cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
|
|
|
|
local num="${1}" where="${2}" chain=
|
|
shift
|
|
|
|
test -z "${where}" && where="OUTPUT"
|
|
for chain in ${where//,/ }
|
|
do
|
|
case "${chain^^}" in
|
|
PRE|PREROUTING) chain="PREROUTING" ;;
|
|
IN|INPUT) chain="INPUT" ;;
|
|
OUT|OUTPUT) chain="OUTPUT" ;;
|
|
PASS|FORWARD) chain="FORWARD" ;;
|
|
POST|POSTROUTING) chain="POSTROUTING" ;;
|
|
esac
|
|
|
|
set_work_function "Rules for TOS on chain '${chain}'"
|
|
rule table mangle chain ${chain} "${@}" action TOS to "${num}"
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
# from http://blog.edseek.com/~jasonb/articles/traffic_shaping/scenarios.html
|
|
tosfix_created_chains4=0
|
|
tosfix_created_chains6=0
|
|
tosfix4() { ipv4 tosfix "${@}"; }
|
|
tosfix6() { ipv6 tosfix "${@}"; }
|
|
tosfix46() { both tosfix "${@}"; }
|
|
tosfix() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
require_work clear || ( error "${FUNCNAME} cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
|
|
|
|
local where="${1}" x= chain=
|
|
shift
|
|
|
|
for x in ipv4 ipv6
|
|
do
|
|
local iptables_cmd=
|
|
case "${x}" in
|
|
ipv4) running_ipv4 || continue
|
|
[ ${tosfix_created_chains4} -eq 1 ] && continue
|
|
tosfix_created_chains4=1
|
|
iptables_cmd="iptables"
|
|
;;
|
|
ipv6) running_ipv6 || continue
|
|
[ ${tosfix_created_chains6} -eq 1 ] && continue
|
|
tosfix_created_chains6=1
|
|
iptables_cmd="ip6tables"
|
|
;;
|
|
esac
|
|
|
|
set_work_function "ackfix chain for ${x}"
|
|
|
|
${iptables_cmd} -t mangle -N ackfix
|
|
${iptables_cmd} -t mangle -A ackfix -m tos ! --tos Normal-Service -j RETURN
|
|
${iptables_cmd} -t mangle -A ackfix -p tcp -m length --length 0:128 -j TOS --set-tos Minimize-Delay
|
|
${iptables_cmd} -t mangle -A ackfix -p tcp -m length --length 128: -j TOS --set-tos Maximize-Throughput
|
|
${iptables_cmd} -t mangle -A ackfix -j RETURN
|
|
|
|
set_work_function "tosfix chain for ${x}"
|
|
|
|
${iptables_cmd} -t mangle -N tosfix
|
|
${iptables_cmd} -t mangle -A tosfix -p tcp -m length --length 0:512 -j RETURN
|
|
${iptables_cmd} -t mangle -A tosfix -m limit --limit 2/s --limit-burst 10 -j RETURN
|
|
${iptables_cmd} -t mangle -A tosfix -j TOS --set-tos Maximize-Throughput
|
|
${iptables_cmd} -t mangle -A tosfix -j RETURN
|
|
done
|
|
|
|
test -z "${where}" && where="PREROUTING POSTROUTING"
|
|
for chain in ${where//,/ }
|
|
do
|
|
case "${chain^^}" in
|
|
PRE|PREROUTING) chain="PREROUTING" ;;
|
|
IN|INPUT) chain="INPUT" ;;
|
|
OUT|OUTPUT) chain="OUTPUT" ;;
|
|
PASS|FORWARD) chain="FORWARD" ;;
|
|
POST|POSTROUTING) chain="POSTROUTING" ;;
|
|
esac
|
|
|
|
set_work_function "Fixing TOS for TCP ACK packets"
|
|
rule table mangle chain ${chain} "${@}" proto tcp custom '-m tcp --tcp-flags SYN,RST,ACK ACK' action ackfix
|
|
|
|
set_work_function "Fixing TOS for Minimize-Delay packets"
|
|
rule table mangle chain ${chain} "${@}" proto tcp custom '-m tos --tos Minimize-Delay' action tosfix
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
dscp4() { ipv4 dscp "${@}"; }
|
|
dscp6() { ipv6 dscp "${@}"; }
|
|
dscp46() { both dscp "${@}"; }
|
|
dscp() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
require_work clear || ( error "${FUNCNAME} cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
|
|
|
|
local value="${1}" class= where=
|
|
shift
|
|
|
|
if [ "${value}" = "class" ]
|
|
then
|
|
value=
|
|
class="${1}"
|
|
shift
|
|
fi
|
|
|
|
where="${1}"
|
|
shift
|
|
|
|
test -z "${where}" && where="OUTPUT"
|
|
for chain in ${where//,/ }
|
|
do
|
|
case "${chain^^}" in
|
|
PRE|PREROUTING) chain="PREROUTING" ;;
|
|
IN|INPUT) chain="INPUT" ;;
|
|
OUT|OUTPUT) chain="OUTPUT" ;;
|
|
PASS|FORWARD) chain="FORWARD" ;;
|
|
POST|POSTROUTING) chain="POSTROUTING" ;;
|
|
esac
|
|
|
|
set_work_function "Rules for DSCP"
|
|
|
|
if [ ! -z "${class}" ]
|
|
then
|
|
rule table mangle chain ${chain} "${@}" action DSCP to class ${class}
|
|
else
|
|
rule table mangle chain ${chain} "${@}" action DSCP to ${value}
|
|
fi
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
tcpmss4() { ipv4 tcpmss "${@}"; }
|
|
tcpmss6() { ipv6 tcpmss "${@}"; }
|
|
tcpmss46() { both tcpmss "${@}"; }
|
|
tcpmss() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
local value="${1}" iface="$2" target=
|
|
|
|
if [ -z "$iface" ]
|
|
then
|
|
if [ ! -z "${work_cmd}" ]
|
|
then
|
|
iface="${work_outface}"
|
|
if [ -z "$iface" ]
|
|
then
|
|
error "${FUNCNAME} cannot find the interfaces to setup. Did you set an outface?"
|
|
return 1
|
|
fi
|
|
|
|
else
|
|
iface="all"
|
|
fi
|
|
fi
|
|
|
|
case "$value" in
|
|
auto)
|
|
target="-j TCPMSS --clamp-mss-to-pmtu"
|
|
;;
|
|
|
|
[0-9]*)
|
|
target="-j TCPMSS --set-mss $value"
|
|
;;
|
|
|
|
*)
|
|
;;
|
|
esac
|
|
|
|
if [ -z "$target" ]
|
|
then
|
|
error "${FUNCNAME} requires either the word 'auto' or a numeric argument for mss."
|
|
return 1
|
|
fi
|
|
|
|
if [ "${iface}" = "all" ]
|
|
then
|
|
set_work_function "TCPMSS for all interfaces"
|
|
|
|
iptables_both -t mangle -A POSTROUTING -p tcp -m tcp --tcp-flags SYN,RST SYN $target
|
|
else
|
|
local f=
|
|
for f in $iface
|
|
do
|
|
set_work_function "TCPMSS for interface '${f}'"
|
|
iptables_both -t mangle -A POSTROUTING -o ${f} -p tcp -m tcp --tcp-flags SYN,RST SYN $target
|
|
done
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
FIREHOL_DOCKER_SETUP_CHAINS=0
|
|
docker_setup_chains() {
|
|
if [ ${FIREHOL_DOCKER_SETUP_CHAINS} -eq 0 ]
|
|
then
|
|
FIREHOL_DOCKER_SETUP_CHAINS=1
|
|
|
|
set_work_function "docker port mapping chain (port forwarding)"
|
|
iptables -t nat -N DOCKER
|
|
|
|
set_work_function "docker inspection of local traffic for port mapping"
|
|
iptables -t nat -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
|
|
iptables -t nat -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
|
|
|
|
set_work_function "docker port mapping chain (filtering - accepts all ports mapped)"
|
|
iptables -t filter -N DOCKER
|
|
|
|
set_work_function "docker-user chain"
|
|
iptables -t filter -N DOCKER-USER
|
|
iptables -t filter -A DOCKER-USER -j RETURN
|
|
iptables -t filter -A FORWARD -j DOCKER-USER
|
|
|
|
set_work_function "docker-isolation chain"
|
|
iptables -t filter -N DOCKER-ISOLATION
|
|
iptables -t filter -A DOCKER-ISOLATION -j RETURN
|
|
iptables -t filter -A FORWARD -j DOCKER-ISOLATION
|
|
fi
|
|
}
|
|
|
|
declare -A DOCKER_SETUP_INTERFACES=()
|
|
docker_setup_interface() {
|
|
local iface="${1}"
|
|
if [ -z "${DOCKER_SETUP_INTERFACES[${iface}]}" ]
|
|
then
|
|
DOCKER_SETUP_INTERFACES[${iface}]="1"
|
|
|
|
set_work_function "no port mapping between containers - the containers are accessing other containers on their native ports"
|
|
iptables -t nat -A DOCKER -i "${iface}" -j RETURN
|
|
|
|
set_work_function "accept the established docker sockets"
|
|
iptables -t filter -A FORWARD -o "${iface}" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
|
|
|
set_work_function "send all forwarded traffic towards containers, to DOCKER chain (accepts all ports mapped)"
|
|
iptables -t filter -A FORWARD -o "${iface}" -j DOCKER
|
|
|
|
set_work_function "accept all traffic from containers to anywhere except containers"
|
|
iptables -t filter -A FORWARD -i "${iface}" ! -o "${iface}" -j ACCEPT
|
|
|
|
set_work_function "accept all traffic between containers"
|
|
iptables -t filter -A FORWARD -i "${iface}" -o "${iface}" -j ACCEPT
|
|
fi
|
|
}
|
|
|
|
docker_bridge() {
|
|
local interfaces="${1}" network="${2}" iface subnet
|
|
|
|
docker_setup_chains
|
|
|
|
for iface in ${interfaces//,/ }
|
|
do
|
|
docker_setup_interface "${iface}"
|
|
|
|
for subnet in ${network//,/ }
|
|
do
|
|
set_work_function "allow the docker containers to reach the world"
|
|
iptables -t nat -A POSTROUTING -s "${subnet}" ! -o "${iface}" -j MASQUERADE
|
|
done
|
|
done
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# INTERNAL FUNCTIONS BELOW THIS POINT - Primary commands
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Check the version required by the configuration file
|
|
# WHY:
|
|
# We have to make sure the configuration file has been written for this version
|
|
# of FireHOL. Note that the version command does not actually check the version
|
|
# of firehol.sh. It checks only its config version number.
|
|
|
|
version() {
|
|
work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
if [ ${1} -gt ${FIREHOL_VERSION} ]
|
|
then
|
|
error "Wrong version. FireHOL is v${FIREHOL_VERSION}, your script requires v${1}. See http://firehol.org/upgrade/#config-version-${FIREHOL_VERSION}"
|
|
fi
|
|
|
|
if [ ${1} -eq 5 ]
|
|
then
|
|
ENABLE_IPV6=0
|
|
FIREHOL_DEFAULT_NAMESPACE=
|
|
FIREHOL_NS_CURR=
|
|
FIREHOL_NS_STACK=()
|
|
warning "Running version 5 config. Update configuration to version 6 for IPv6 support. See http://firehol.org/upgrade/#config-version-${FIREHOL_VERSION}"
|
|
fi
|
|
}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# PRIMARY COMMAND: interface
|
|
# Setup rules specific to an interface (physical or logical)
|
|
|
|
interface4() { ipv4 interface "${@}"; }
|
|
interface6() { ipv6 interface "${@}"; }
|
|
interface46() { both interface "${@}"; }
|
|
interface() {
|
|
work_realcmd_primary ${FUNCNAME} "${@}"
|
|
|
|
# --- close any open command ---
|
|
|
|
close_cmd || return 1
|
|
|
|
# --- test prerequisites ---
|
|
|
|
require_work clear || return 1
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
local ipv="${FIREHOL_NS_CURR}"
|
|
if [ "z${1}" = "z-ns" ]
|
|
then
|
|
ipv="${2}"
|
|
shift 2
|
|
fi
|
|
push_namespace "${ipv}"
|
|
|
|
# --- get parameters and validate them ---
|
|
|
|
# Get the interface
|
|
local inface="${1}" \
|
|
name="${2}"
|
|
shift 2
|
|
|
|
test -z "${inface}" && error "real interface is not set" && return 1
|
|
test -z "${name}" && error "${FUNCNAME} name is not set" && return 1
|
|
|
|
# --- do the job ---
|
|
|
|
work_cmd="${FUNCNAME}"
|
|
work_name="${name}"
|
|
work_realcmd=("(unset)")
|
|
|
|
set_work_function -ne "Rules for ${FUNCNAME} '${work_name}'"
|
|
|
|
create_chain filter "in_${work_name}" INPUT push_flow_inheritance in in set_work_inface "${@}" inface "${inface}" outface any || return 1
|
|
create_chain filter "out_${work_name}" OUTPUT push_flow_inheritance out out set_work_outface reverse "${@}" inface "${inface}" outface any || return 1
|
|
|
|
# accepting RELATED ICMP/ICMPv6 packets
|
|
set_work_function "Accepting all ICMP RELATED sockets in interface '${work_name}'"
|
|
|
|
if running_ipv4
|
|
then
|
|
push_namespace ipv4
|
|
rule chain "in_${work_name}" state RELATED proto icmp action ACCEPT || return 1
|
|
rule chain "out_${work_name}" state RELATED proto icmp action ACCEPT || return 1
|
|
pop_namespace
|
|
fi
|
|
if running_ipv6
|
|
then
|
|
push_namespace ipv6
|
|
rule chain "in_${work_name}" state RELATED proto icmpv6 action ACCEPT || return 1
|
|
rule chain "out_${work_name}" state RELATED proto icmpv6 action ACCEPT || return 1
|
|
pop_namespace
|
|
fi
|
|
|
|
set_work_function "Accepting TCP-RESET on the output of interface '${work_name}'"
|
|
rule chain "out_${work_name}" state RELATED proto tcp custom '--tcp-flags ALL ACK,RST' action ACCEPT || return 1
|
|
|
|
return 0
|
|
}
|
|
|
|
router4() { ipv4 router "${@}"; }
|
|
router6() { ipv6 router "${@}"; }
|
|
router46() { both router "${@}"; }
|
|
router() {
|
|
work_realcmd_primary ${FUNCNAME} "${@}"
|
|
|
|
# --- close any open command ---
|
|
|
|
close_cmd || return 1
|
|
|
|
# --- test prerequisites ---
|
|
|
|
require_work clear || return 1
|
|
set_work_function -ne "Initializing ${FUNCNAME}"
|
|
|
|
local ipv="${FIREHOL_NS_CURR}"
|
|
if [ "z${1}" = "z-ns" ]
|
|
then
|
|
ipv="${2}"
|
|
shift 2
|
|
fi
|
|
push_namespace "${ipv}"
|
|
|
|
|
|
# --- get parameters and validate them ---
|
|
|
|
# Get the name for this router
|
|
local name="${1}"; shift
|
|
test -z "${name}" && error "${FUNCNAME} name is not set" && return 1
|
|
|
|
|
|
# --- do the job ---
|
|
|
|
work_cmd="${FUNCNAME}"
|
|
work_name="${name}"
|
|
work_realcmd=("(unset)")
|
|
|
|
set_work_function -ne "Rules for ${FUNCNAME} '${work_name}'"
|
|
|
|
create_chain filter "in_${work_name}" FORWARD push_flow_inheritance in in set_work_inface set_work_outface "${@}" || return 1
|
|
create_chain filter "out_${work_name}" FORWARD push_flow_inheritance out out reverse "${@}" || return 1
|
|
|
|
set_work_function "Accepting all ICMP RELATED sockets in router '${work_name}'"
|
|
|
|
if running_ipv4
|
|
then
|
|
push_namespace ipv4
|
|
rule chain "in_${work_name}" state RELATED proto icmp action ACCEPT || return 1
|
|
rule chain "out_${work_name}" state RELATED proto icmp action ACCEPT || return 1
|
|
pop_namespace
|
|
fi
|
|
if running_ipv6
|
|
then
|
|
push_namespace ipv6
|
|
rule chain "in_${work_name}" state RELATED proto icmpv6 action ACCEPT || return 1
|
|
rule chain "out_${work_name}" state RELATED proto icmpv6 action ACCEPT || return 1
|
|
pop_namespace
|
|
fi
|
|
|
|
set_work_function "Accepting TCP-RESET on router '${work_name}'"
|
|
rule chain "in_${work_name}" state RELATED proto tcp custom '--tcp-flags ALL ACK,RST' action ACCEPT || return 1
|
|
rule chain "out_${work_name}" state RELATED proto tcp custom '--tcp-flags ALL ACK,RST' action ACCEPT || return 1
|
|
|
|
FIREHOL_ROUTING=1
|
|
return 0
|
|
}
|
|
|
|
save_for_restore() {
|
|
local check="$1"; shift
|
|
|
|
printf "%q " "${@}" >&20
|
|
|
|
if [ "${check}" = "none" -o "${check}" = "warn" ]
|
|
then
|
|
printf " || echo >/dev/null\n" >&20
|
|
else
|
|
printf " || exit 1\n" >&20
|
|
fi
|
|
}
|
|
|
|
postprocess() {
|
|
# if the caller is not from the program file, get the config line calling us
|
|
[ ! "${BASH_SOURCE[1]}" = "${PROGRAM_FILE}" ] && work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
local check="error" save=1
|
|
while [ ! "A${1}" = "A" ]
|
|
do
|
|
case "A${1}" in
|
|
A-ne) shift; check="none";;
|
|
A-warn) shift; check="warn";;
|
|
A-ns) shift; save=0;;
|
|
*) break;;
|
|
esac
|
|
done
|
|
|
|
if [ "${FIREHOL_MODE}" = "EXPLAIN" ]
|
|
then
|
|
printf "%q " "${@}"
|
|
printf "\n"
|
|
return 0
|
|
elif [ "${FIREHOL_MODE}" = "DEBUG" ]
|
|
then
|
|
check="debug"
|
|
fi
|
|
|
|
printf "%q " "${@}" >&21
|
|
case "${check}" in
|
|
debug) printf "\n" >&21
|
|
;;
|
|
|
|
none) printf " >/dev/null 2>&1 || echo >/dev/null\n" >&21
|
|
;;
|
|
|
|
warn|error)
|
|
# do not run config_line here, it is very slow
|
|
# config_line -ne
|
|
printf " >${FIREHOL_OUTPUT}.log 2>&1 || runtime_error ${check} \$? '${LAST_CONFIG_LINE}' " >&21
|
|
printf "%q " "${@}" >&21
|
|
printf "\n" >&21
|
|
;;
|
|
esac
|
|
|
|
test $save -eq 1 && save_for_restore ${check} "${@}"
|
|
|
|
return 0
|
|
}
|
|
|
|
postprocess2() {
|
|
# if the caller is not from the program file, get the config line calling us
|
|
[ ! "${BASH_SOURCE[1]}" = "${PROGRAM_FILE}" ] && work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
local check="error" save=1
|
|
while [ ! "A${1}" = "A" ]
|
|
do
|
|
case "A${1}" in
|
|
A-ne) shift; check="none";;
|
|
A-warn) shift; check="warn";;
|
|
A-ns) shift; save=0;;
|
|
*) break;;
|
|
esac
|
|
done
|
|
|
|
if [ "${FIREHOL_MODE}" = "EXPLAIN" ]
|
|
then
|
|
printf "%q " "${@}"
|
|
printf "\n"
|
|
return 0
|
|
elif [ "${FIREHOL_MODE}" = "DEBUG" ]
|
|
then
|
|
check="debug"
|
|
fi
|
|
|
|
printf "%q " "${@}" >&22
|
|
case "${check}" in
|
|
debug) printf "\n" >&22
|
|
;;
|
|
|
|
none) printf " >/dev/null 2>&1 || echo >/dev/null\n" >&22
|
|
;;
|
|
|
|
warn|error)
|
|
# do not run config_line here, it is very slow
|
|
# config_line -ne
|
|
printf " >${FIREHOL_OUTPUT}.log 2>&1 || runtime_error ${check} \$? '${LAST_CONFIG_LINE}' " >&22
|
|
printf "%q " "${@}" >&22
|
|
printf "\n" >&22
|
|
;;
|
|
esac
|
|
|
|
test $save -eq 1 && save_for_restore ${check} "${@}"
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# fast activation
|
|
# in fast activation mode we catch all /sbin/iptables commands and instead of
|
|
# executing them, we generate an iptables-restore compatible file.
|
|
|
|
run_fast() {
|
|
local n=table \
|
|
t=filter \
|
|
cmd="$1" \
|
|
log=
|
|
shift
|
|
|
|
[ "${cmd}" = "ip6tables" ] && n=table6
|
|
|
|
if [ "z${1}" = "z-t" ]
|
|
then
|
|
t="${2}"
|
|
shift 2
|
|
fi
|
|
|
|
#[ "${FIREHOL_FAST_ACTIVATION_TRACE}"" = "1" ] && log="${*}"
|
|
case "$1" in
|
|
-P) echo ":$2 $3 [0:0]" >>"${FIREHOL_DIR}/fast/${n}.${t}.policy"
|
|
;;
|
|
|
|
-N) echo ":$2 - [0:0]" >>"${FIREHOL_DIR}/fast/${n}.${t}.chains"
|
|
;;
|
|
|
|
-A) #[ "${FIREHOL_FAST_ACTIVATION_TRACE}" = "1" ] && echo "${log/ -j */ -j NFLOG --nflog-prefix=\"${t}.${2} ${work_function}\"}" >>"${FIREHOL_DIR}/fast/${n}.${t}.rules"
|
|
echo "${*}" >>"${FIREHOL_DIR}/fast/${n}.${t}.rules"
|
|
;;
|
|
|
|
-I) echo "${*}" >>"${FIREHOL_DIR}/fast/${n}.${t}.rules"
|
|
#[ "${FIREHOL_FAST_ACTIVATION_TRACE}" = "1" ] && echo "${log/ -j */ -j NFLOG --nflog-prefix=\"${t}.${2} ${work_function}\"}" >>"${FIREHOL_DIR}/fast/${n}.${t}.rules"
|
|
;;
|
|
|
|
# if it is none of the above, we execute it normally.
|
|
*) echo >&2 "WARNING: Ignoring command '${cmd} -t ${t} ${@}'"
|
|
;;
|
|
esac
|
|
|
|
test ! -f "${FIREHOL_DIR}/fast/${n}s/${t}" && ${TOUCH_CMD} "${FIREHOL_DIR}/fast/${n}s/${t}"
|
|
return 0
|
|
}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# INTERNAL FUNCTIONS BELOW THIS POINT - Sub-commands
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Change the policy of an interface
|
|
# WHY:
|
|
# Not all interfaces have the same policy. The admin must have control over it.
|
|
# Here we just set what the admin wants. At the interface finalization we
|
|
# produce the iptables rules.
|
|
|
|
policy() {
|
|
work_realcmd_secondary ${FUNCNAME} "${@}"
|
|
|
|
require_work set any || return 1
|
|
|
|
set_work_function "Policy of ${work_name} to ${1}"
|
|
work_policy="$*"
|
|
|
|
return 0
|
|
}
|
|
|
|
server4() { ipv4 server "${@}"; }
|
|
server6() { ipv6 server "${@}"; }
|
|
server46() { both server "${@}"; }
|
|
server() {
|
|
work_realcmd_secondary ${FUNCNAME} "${@}"
|
|
|
|
require_work set any || return 1
|
|
smart_function server "${@}"
|
|
return $?
|
|
}
|
|
|
|
client4() { ipv4 client "${@}"; }
|
|
client6() { ipv6 client "${@}"; }
|
|
client46() { both client "${@}"; }
|
|
client() {
|
|
work_realcmd_secondary ${FUNCNAME} "${@}"
|
|
|
|
require_work set any || return 1
|
|
smart_function client "${@}"
|
|
return $?
|
|
}
|
|
|
|
route4() { ipv4 route "${@}"; }
|
|
route6() { ipv6 route "${@}"; }
|
|
route46() { both route "${@}"; }
|
|
route() {
|
|
work_realcmd_secondary ${FUNCNAME} "${@}"
|
|
|
|
require_work set router || return 1
|
|
smart_function server "${@}"
|
|
return $?
|
|
}
|
|
|
|
|
|
# --- protection ---------------------------------------------------------------
|
|
|
|
FIREHOL_PROTECTION_COUNT=0
|
|
|
|
protection() {
|
|
work_realcmd_secondary ${FUNCNAME} "${@}"
|
|
|
|
require_work set any || return 1
|
|
|
|
local in="in" \
|
|
prface="${work_inface}" \
|
|
pre="pr" \
|
|
reverse= \
|
|
x=
|
|
|
|
if [ "${1}" = "reverse" ]
|
|
then
|
|
reverse="reverse" # needed to recursion
|
|
pre="prr" # in case a router has protections
|
|
# both ways, the second needs to
|
|
# have different chain names
|
|
|
|
in="out" # reverse the interface
|
|
|
|
prface="${work_outface}"
|
|
shift
|
|
fi
|
|
|
|
local type="${1}" rate= burst=
|
|
shift
|
|
|
|
set_work_function -ne "Rules for rotections on '${prface}' for ${work_cmd} '${work_name}'"
|
|
|
|
for x in ${type//,/ }
|
|
do
|
|
FIREHOL_PROTECTION_COUNT=$[FIREHOL_PROTECTION_COUNT + 1]
|
|
|
|
case "${x,,}" in
|
|
none)
|
|
return 0
|
|
;;
|
|
|
|
bad-packets)
|
|
protection ${reverse} "fragments new-tcp-w/o-syn malformed-xmas malformed-null malformed-bad invalid" "${@}"
|
|
return $?
|
|
;;
|
|
|
|
strong|full|all)
|
|
protection ${reverse} "fragments new-tcp-w/o-syn icmp-floods syn-floods malformed-xmas malformed-null malformed-bad invalid" "${@}"
|
|
return $?
|
|
;;
|
|
|
|
invalid)
|
|
if [ "${FIREHOL_DROP_INVALID}" -eq 0 ]
|
|
then
|
|
set_work_function "Rules to ${FIREHOL_DROP_INVALID_ACTION} invalid packets on '${prface}' for ${work_cmd} '${work_name}'"
|
|
|
|
rule in chain "${in}_${work_name}" state INVALID action ${FIREHOL_DROP_INVALID_ACTION} || return 1
|
|
fi
|
|
;;
|
|
|
|
connrate)
|
|
local mychain="${pre}_${work_name}_crt${FIREHOL_PROTECTION_COUNT}"
|
|
create_chain filter "${mychain}" "${in}_${work_name}" in state NEW || return 1
|
|
|
|
set_work_function "Rules for enforcing connection rate per client on '${prface}' for ${work_cmd} '${work_name}'"
|
|
|
|
rule in chain "${mychain}" hashlimit "${pre}_${work_name}_connrate" upto "${rate}" mode srcip "${@}" action return || return 1
|
|
rule in chain "${mychain}" loglimit "BLOCKED CLIENT CONNECTION RATE REACHED" action drop || return 1
|
|
;;
|
|
|
|
connlimit)
|
|
local mychain="${pre}_${work_name}_clm${FIREHOL_PROTECTION_COUNT}"
|
|
create_chain filter "${mychain}" "${in}_${work_name}" in state NEW || return 1
|
|
|
|
set_work_function "Rules for enforcing connection limit per client on '${prface}' for ${work_cmd} '${work_name}'"
|
|
|
|
rule in chain "${mychain}" connlimit saddr upto "${@}" action return || return 1
|
|
rule in chain "${mychain}" loglimit "BLOCKED CLIENT CONNECTION LIMIT REACHED" action drop || return 1
|
|
;;
|
|
|
|
fragments)
|
|
# not needed - no use with connection tracking
|
|
# if running_ipv4; then
|
|
# push_namespace ipv4
|
|
# local frag_status=0 mychain="${pre}_${work_name}_fragments"
|
|
# create_chain filter "${mychain}" "${in}_${work_name}" in custom "-f" || frag_status=$[frag_status+1]
|
|
#
|
|
# set_work_function "Rules for protection from packet fragments on '${prface}' for ${work_cmd} '${work_name}'"
|
|
#
|
|
# rule in chain "${mychain}" loglimit "BLOCKED PACKET FRAGMENTS" action drop || frag_status=$[frag_status+1]
|
|
# pop_namespace
|
|
# if [ $frag_status -gt 0 ]
|
|
# then
|
|
# return 1
|
|
# fi
|
|
# fi
|
|
# # IPv6 packet fragments can be used to
|
|
# # evade stateless firewalls. FireHOL
|
|
# # creates a stateful firewall with connection
|
|
# # tracking, so fragments will be reassembled
|
|
# # before checking.
|
|
;;
|
|
|
|
new-tcp-w/o-syn)
|
|
local mychain="${pre}_${work_name}_nsn${FIREHOL_PROTECTION_COUNT}"
|
|
create_chain filter "${mychain}" "${in}_${work_name}" in proto tcp state NEW custom "! --syn" || return 1
|
|
|
|
set_work_function "Rules for protection from new TCP connections without the SYN flag set on '${prface}' for ${work_cmd} '${work_name}'"
|
|
|
|
rule in chain "${mychain}" loglimit "BLOCKED NEW TCP w/o SYN" action drop || return 1
|
|
;;
|
|
|
|
icmp-floods)
|
|
local mychain="${pre}_${work_name}_ifl${FIREHOL_PROTECTION_COUNT}"
|
|
|
|
if running_ipv4; then
|
|
ipv4 create_chain filter "${mychain}" "${in}_${work_name}" in proto icmp custom "--icmp-type echo-request" || return 1
|
|
fi
|
|
if running_ipv6; then
|
|
ipv6 create_chain filter "${mychain}" "${in}_${work_name}" in proto icmpv6 custom "--icmpv6-type echo-request" || return 1
|
|
fi
|
|
|
|
set_work_function "Rules for protection from ICMP floods on '${prface}' for ${work_cmd} '${work_name}'"
|
|
|
|
rate="${1-100/s}"
|
|
burst="${2-50}"
|
|
|
|
rule in chain "${mychain}" limit "${rate}" "${burst}" action return || return 1
|
|
rule in chain "${mychain}" loglimit "BLOCKED ICMP FLOOD" action drop || return 1
|
|
;;
|
|
|
|
syn-floods)
|
|
local mychain="${pre}_${work_name}_sfl${FIREHOL_PROTECTION_COUNT}"
|
|
create_chain filter "${mychain}" "${in}_${work_name}" in proto tcp custom "--syn" || return 1
|
|
|
|
set_work_function "Rules for protection from TCP SYN floods on '${prface}' for ${work_cmd} '${work_name}'"
|
|
|
|
rate="${1-100/s}"
|
|
burst="${2-50}"
|
|
|
|
rule in chain "${mychain}" limit "${rate}" "${burst}" action return || return 1
|
|
rule in chain "${mychain}" loglimit "BLOCKED SYN FLOOD" action drop || return 1
|
|
;;
|
|
|
|
all-floods)
|
|
local mychain="${pre}_${work_name}_afl${FIREHOL_PROTECTION_COUNT}"
|
|
create_chain filter "${mychain}" "${in}_${work_name}" in state NEW || return 1
|
|
|
|
set_work_function "Rules for protection from ALL floods on '${prface}' for ${work_cmd} '${work_name}'"
|
|
|
|
rate="${1-100/s}"
|
|
burst="${2-50}"
|
|
|
|
rule in chain "${mychain}" limit "${rate}" "${burst}" action return || return 1
|
|
rule in chain "${mychain}" loglimit "BLOCKED ALL FLOOD" action drop || return 1
|
|
;;
|
|
|
|
malformed-xmas)
|
|
local mychain="${pre}_${work_name}_mxs${FIREHOL_PROTECTION_COUNT}"
|
|
create_chain filter "${mychain}" "${in}_${work_name}" in proto tcp custom "--tcp-flags ALL ALL" || return 1
|
|
|
|
set_work_function "Rules for protection from packets with all TCP flags set on '${prface}' for ${work_cmd} '${work_name}'"
|
|
|
|
rule in chain "${mychain}" loglimit "BLOCKED MALFORMED XMAS" action drop || return 1
|
|
;;
|
|
|
|
malformed-null)
|
|
local mychain="${pre}_${work_name}_mnl${FIREHOL_PROTECTION_COUNT}"
|
|
create_chain filter "${mychain}" "${in}_${work_name}" in proto tcp custom "--tcp-flags ALL NONE" || return 1
|
|
|
|
set_work_function "Rules for protection from packets with all TCP flags unset on '${prface}' for ${work_cmd} '${work_name}'"
|
|
|
|
rule in chain "${mychain}" loglimit "BLOCKED MALFORMED NULL" action drop || return 1
|
|
;;
|
|
|
|
malformed-bad)
|
|
local mychain="${pre}_${work_name}_mbd${FIREHOL_PROTECTION_COUNT}"
|
|
# PSACAN2-FIN (SYN,FIN SYN,FIN) (FIN after SYN)
|
|
create_chain filter "${mychain}" "${in}_${work_name}" in proto tcp custom "--tcp-flags SYN,FIN SYN,FIN" || return 1
|
|
|
|
set_work_function "Rules for protection from packets with illegal TCP flags on '${prface}' for ${work_cmd} '${work_name}'"
|
|
|
|
# SYN-RST (SYN,RST SYN,RST) (RST after SYN)
|
|
rule in chain "${in}_${work_name}" action "${mychain}" proto tcp custom "--tcp-flags SYN,RST SYN,RST" || return 1
|
|
# PSCAN (ALL SYN,RST,ACK,FIN,URG)
|
|
rule in chain "${in}_${work_name}" action "${mychain}" proto tcp custom "--tcp-flags ALL SYN,RST,ACK,FIN,URG" || return 1
|
|
# NAME-XMAS-SCAN (ALL FIN,URG,PSH)
|
|
rule in chain "${in}_${work_name}" action "${mychain}" proto tcp custom "--tcp-flags ALL FIN,URG,PSH" || return 1
|
|
# SYNFIN-SCAN (ALL SYN,FIN)
|
|
rule in chain "${in}_${work_name}" action "${mychain}" proto tcp custom "--tcp-flags ALL SYN,FIN" || return 1
|
|
# FIN-SCAN (ALL FIN)
|
|
rule in chain "${in}_${work_name}" action "${mychain}" proto tcp custom "--tcp-flags ALL FIN" || return 1
|
|
# NMAP-ID (ALL URG,PSH,SYN,FIN)
|
|
rule in chain "${in}_${work_name}" action "${mychain}" proto tcp custom "--tcp-flags ALL URG,PSH,SYN,FIN" || return 1
|
|
# FIN (ACK,FIN FIN) (FIN without ACK)
|
|
rule in chain "${in}_${work_name}" action "${mychain}" proto tcp custom "--tcp-flags ACK,FIN FIN" || return 1
|
|
# PSH (ACK,PSH, PSH) (PSH without ACK)
|
|
rule in chain "${in}_${work_name}" action "${mychain}" proto tcp custom "--tcp-flags ACK,PSH PSH" || return 1
|
|
# URG (ACK,URG URG) (URG without ACK)
|
|
rule in chain "${in}_${work_name}" action "${mychain}" proto tcp custom "--tcp-flags ACK,URG URG" || return 1
|
|
# PSCAN2-RST (FIN,RST FIN,RST) (RST after FIN)
|
|
rule in chain "${in}_${work_name}" action "${mychain}" proto tcp custom "--tcp-flags FIN,RST FIN,RST" || return 1
|
|
|
|
rule in chain "${mychain}" loglimit "BLOCKED MALFORMED BAD" action drop || return 1
|
|
;;
|
|
|
|
*)
|
|
error "Protection '${x}' does not exists."
|
|
return 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# KERNEL MODULE MANAGEMENT
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Manage kernel modules
|
|
# WHY:
|
|
# We need to load a set of kernel modules during postprocessing, and after the
|
|
# new firewall has been activated.
|
|
# The whole point of the following code, is not to attempt loading modules when
|
|
# they are compiled into the kernel.
|
|
|
|
# Try to find the current kernel configuration
|
|
KERNEL_CONFIG=
|
|
if [ -f "/proc/config" ]
|
|
then
|
|
KERNEL_CONFIG="/proc/config"
|
|
${CAT_CMD} /proc/config >"${FIREHOL_DIR}/kcfg" || KERNEL_CONFIG=
|
|
fi
|
|
|
|
if [ -z "${KERNEL_CONFIG}" -a -f "/proc/config.gz" ]
|
|
then
|
|
KERNEL_CONFIG="/proc/config.gz"
|
|
common_require_cmd $PROGRAM_FILE ZCAT_CMD
|
|
${ZCAT_CMD} /proc/config.gz >"${FIREHOL_DIR}/kcfg" || KERNEL_CONFIG=
|
|
fi
|
|
|
|
if [ -z "${KERNEL_CONFIG}" -a -f "/lib/modules/`${UNAME_CMD} -r`/build/.config" ]
|
|
then
|
|
KERNEL_CONFIG="/lib/modules/`${UNAME_CMD} -r`/build/.config"
|
|
${CAT_CMD} "${KERNEL_CONFIG}" >"${FIREHOL_DIR}/kcfg" || KERNEL_CONFIG=
|
|
fi
|
|
|
|
if [ -z "${KERNEL_CONFIG}" -a -f "/boot/config-`${UNAME_CMD} -r`" ]
|
|
then
|
|
KERNEL_CONFIG="/boot/config-`${UNAME_CMD} -r`"
|
|
${CAT_CMD} "${KERNEL_CONFIG}" >"${FIREHOL_DIR}/kcfg" || KERNEL_CONFIG=
|
|
fi
|
|
|
|
if [ -z "${KERNEL_CONFIG}" -a -f "/usr/src/linux/.config" ]
|
|
then
|
|
KERNEL_CONFIG="/usr/src/linux/.config"
|
|
${CAT_CMD} "${KERNEL_CONFIG}" >"${FIREHOL_DIR}/kcfg" || KERNEL_CONFIG=
|
|
fi
|
|
|
|
# Did we managed to find the kernel configuration?
|
|
if [ ! -z "${KERNEL_CONFIG}" -a -s "${FIREHOL_DIR}/kcfg" ]
|
|
then
|
|
# We found a kernel configuration
|
|
|
|
# Load all the definitions for CONFIG_*_NF_* variables
|
|
# We grep what we care for, to make sure there is no garbage or malicious code
|
|
# in the file we will run.
|
|
${CAT_CMD} "${FIREHOL_DIR}/kcfg" | ${GREP_CMD} -e "^CONFIG_[A-Z0-9_]\+_NF_[A-Z0-9_]\+=[ynm]$" >"${FIREHOL_DIR}/kcfg.nf"
|
|
|
|
# run it to get the variables
|
|
source "${FIREHOL_DIR}/kcfg.nf"
|
|
else
|
|
# We could not find a kernel configuration
|
|
|
|
KERNEL_CONFIG=
|
|
|
|
if [ ! ${FIREHOL_LOAD_KERNEL_MODULES} -eq 0 ]
|
|
then
|
|
echo >&2 " "
|
|
echo >&2 " WARNING:"
|
|
echo >&2 " --------"
|
|
echo >&2 " FireHOL cannot find your current kernel configuration."
|
|
echo >&2 " Please, either compile your kernel with /proc/config,"
|
|
echo >&2 " or make sure there is a valid kernel config in:"
|
|
echo >&2 " /usr/src/linux/.config"
|
|
echo >&2 " "
|
|
echo >&2 " Because of this, FireHOL will simply attempt to load"
|
|
echo >&2 " all kernel modules for the services used, without"
|
|
echo >&2 " being able to detect failures."
|
|
echo >&2 " "
|
|
$SLEEP_CMD 2
|
|
fi
|
|
|
|
fi
|
|
|
|
# activation-phase command to check for the existence of
|
|
# a kernel configuration directive. It returns:
|
|
# 0 = module is already in the kernel
|
|
# 1 = module can be loaded with modprobe
|
|
# 2 = no info about this module in the kernel
|
|
check_kernel_config() {
|
|
# In kernels 2.6.20+ _IP_ was removed from kernel iptables config names.
|
|
# A few kernels have _CONNTRACT_ replaced with _CT_ for certain modules.
|
|
# Try all versions.
|
|
|
|
# the original way
|
|
eval local kcfg1="\$${1}"
|
|
|
|
# without _IP_
|
|
local t=`echo ${1} | ${SED_CMD} "s/_IP_//g"`
|
|
eval local kcfg2="\$${t}"
|
|
|
|
# _CONNTRACK_ as _CT_
|
|
local t=`echo ${1} | ${SED_CMD} "s/_CONNTRACK_/_CT_/g"`
|
|
eval local kcfg3="\$${t}"
|
|
|
|
# prefer the kernel 2.6.20+ way
|
|
if [ ! -z "${kcfg2}" ]
|
|
then
|
|
kcfg="${kcfg2}"
|
|
|
|
elif [ ! -z "${kcfg3}" ]
|
|
then
|
|
kcfg="${kcfg3}"
|
|
else
|
|
kcfg="${kcfg1}"
|
|
fi
|
|
|
|
case ${kcfg} in
|
|
y) return 0
|
|
;;
|
|
|
|
m) return 1
|
|
;;
|
|
|
|
*) return 2
|
|
;;
|
|
esac
|
|
|
|
return 2
|
|
}
|
|
|
|
# activation-phase command to check for the existence of
|
|
# a kernel module. It returns:
|
|
# 0 = module is already in the kernel
|
|
# 1 = module can be loaded with modprobe
|
|
# 2 = no info about this module in the kernel
|
|
check_kernel_module() {
|
|
local mod="${1}"
|
|
|
|
case ${mod} in
|
|
ip_tables)
|
|
test -f /proc/net/ip_tables_names && return 0
|
|
check_kernel_config CONFIG_IP_NF_IPTABLES
|
|
test $? -ne 0 && check_kernel_config CONFIG_NF_TABLES_IPV4
|
|
return $?
|
|
;;
|
|
|
|
ip6_tables)
|
|
test -f /proc/net/ip6_tables_names && return 0
|
|
check_kernel_config CONFIG_NF_TABLES_IPV6
|
|
return $?
|
|
;;
|
|
|
|
ip_conntrack|nf_conntrack)
|
|
test -f /proc/net/ip_conntrack -o -f /proc/net/nf_conntrack && return 0
|
|
check_kernel_config CONFIG_IP_NF_CONNTRACK
|
|
test $? -ne 0 && check_kernel_config CONFIG_NF_CONNTRACK_IPV4
|
|
return $?
|
|
;;
|
|
|
|
ip_conntrack_*|nf_conntrack_*)
|
|
local mnam="CONFIG_IP_NF_`echo ${mod} | ${CUT_CMD} -d '_' -f 3- | ${TR_CMD} a-z A-Z`"
|
|
check_kernel_config ${mnam}
|
|
return $?
|
|
;;
|
|
|
|
ip_nat_*|nf_nat_*)
|
|
local mnam="CONFIG_IP_NF_NAT_`echo ${mod} | ${CUT_CMD} -d '_' -f 3- | ${TR_CMD} a-z A-Z`"
|
|
check_kernel_config ${mnam}
|
|
return $?
|
|
;;
|
|
|
|
*)
|
|
return 2
|
|
;;
|
|
esac
|
|
|
|
return 2
|
|
}
|
|
|
|
# activation-phase command to load a kernel module.
|
|
LOADED_KERNEL_MODULES=
|
|
load_kernel_module() {
|
|
local mod="${1}"
|
|
|
|
if [ ! ${FIREHOL_LOAD_KERNEL_MODULES} -eq 0 ]
|
|
then
|
|
local m=
|
|
for m in ${LOADED_KERNEL_MODULES}
|
|
do
|
|
test "${m}" = "${mod}" && return 0
|
|
done
|
|
LOADED_KERNEL_MODULES="${LOADED_KERNEL_MODULES} ${mod}"
|
|
|
|
common_require_cmd $PROGRAM_FILE MODPROBE_CMD
|
|
${MODPROBE_CMD} ${mod}
|
|
if [ $? -gt 0 -a $? -ne 17 ] # 17: insmod, already loaded
|
|
then
|
|
check_kernel_module ${mod} || runtime_error warn 1 "$(config_line)" ${MODPROBE_CMD} ${mod}
|
|
fi
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Processing-phase command to tell FireHOL to find one or more
|
|
# kernel modules to load, during activation-phase.
|
|
require_kernel_module() {
|
|
[ ! "${BASH_SOURCE[1]}" = "${PROGRAM_FILE}" ] && work_realcmd_helper ${FUNCNAME} "${@}"
|
|
|
|
local new="${1}"
|
|
|
|
if [ -z "${FIREHOL_KERNEL_MODULES[$new]}" ]
|
|
then
|
|
set_work_function "Adding kernel module '${new}' in the list of kernel modules to load"
|
|
FIREHOL_KERNEL_MODULES[$new]="1"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# INTERNAL FUNCTIONS BELOW THIS POINT - FireHOL internals
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
set_work_function() {
|
|
local show_explain=1
|
|
test "$1" = "-ne" && shift && show_explain=0
|
|
|
|
work_function="$*"
|
|
|
|
if [ "${FIREHOL_MODE}" = "EXPLAIN" ]
|
|
then
|
|
test ${show_explain} -eq 1 && printf "\n# %s\n" "$*"
|
|
elif [ ${FIREHOL_CONF_SHOW} -eq 1 ]
|
|
then
|
|
test ${show_explain} -eq 1 && printf "\n# INFO>>> %s\n" "$*" >&21
|
|
fi
|
|
}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Check the status of the current primary command.
|
|
# WHY:
|
|
# Some sanity check for the order of commands in the configuration file.
|
|
# Each function has a "require_work type command" in order to check that it is
|
|
# placed in a valid point. This means that if you place a "route" command in an
|
|
# interface section (and many other combinations) it will fail.
|
|
|
|
require_work() {
|
|
local type="${1}" \
|
|
cmd="${2}"
|
|
|
|
case "${type}" in
|
|
clear)
|
|
test ! -z "${work_cmd}" && error "Previous work was not applied." && return 1
|
|
;;
|
|
|
|
set)
|
|
test -z "${work_cmd}" && error "The command used requires that a primary command is set." && return 1
|
|
test ! "${work_cmd}" = "${cmd}" -a ! "${cmd}" = "any" && error "Primary command is '${work_cmd}' but '${cmd}' is required." && return 1
|
|
;;
|
|
|
|
*)
|
|
error "Unknown work status '${type}'."
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Finalizes the rules of the last primary command.
|
|
# WHY:
|
|
# At the end of an interface or router we need to add some code to apply its
|
|
# policy, etc.
|
|
# Finalization occurs automatically when a new primary command is executed and
|
|
# when the configuration file finishes.
|
|
|
|
close_cmd() {
|
|
set_work_function -ne "Closing last open primary command (${work_cmd}/${work_name})"
|
|
|
|
case "${work_cmd}" in
|
|
interface)
|
|
close_interface || return 1
|
|
;;
|
|
|
|
router)
|
|
close_router || return 1
|
|
;;
|
|
|
|
'')
|
|
;;
|
|
|
|
*)
|
|
error "Unknown work '${work_cmd}'."
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
# Reset the current status variables to empty/default
|
|
work_counter4=0
|
|
work_counter6=0
|
|
work_cmd=
|
|
work_realcmd=("(unset)")
|
|
work_name=
|
|
work_inface=
|
|
work_outface=
|
|
work_policy=
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# close_interface
|
|
# WHY:
|
|
# Finalizes the rules for the last interface().
|
|
|
|
close_interface() {
|
|
require_work set interface || return 1
|
|
|
|
close_all_groups
|
|
|
|
set_work_function "Finilizing interface '${work_name}'"
|
|
|
|
pop_flow_inheritance
|
|
|
|
# make sure we have a policy
|
|
test -z "${work_policy}" && work_policy="${DEFAULT_INTERFACE_POLICY}"
|
|
local inlog=() outlog=()
|
|
local drop_noise=1
|
|
case "${work_policy}" in
|
|
return|RETURN)
|
|
set_work_function "Nothing to be done for policy RETURN of interface '${work_name}'"
|
|
pop_namespace
|
|
return 0
|
|
;;
|
|
|
|
accept|ACCEPT)
|
|
drop_noise=0
|
|
;;
|
|
|
|
*)
|
|
inlog=(loglimit "${work_policy/ */} UNMATCHED IN-${work_name}")
|
|
outlog=(loglimit "${work_policy/ */} UNMATCHED OUT-${work_name}")
|
|
;;
|
|
esac
|
|
|
|
if [ $drop_noise -eq 1 ]; then
|
|
if running_ipv4; then
|
|
firewall_filtering_policy_common_late iptables INPUT "in_${work_name}"
|
|
firewall_filtering_policy_common_late iptables OUTPUT "out_${work_name}"
|
|
fi
|
|
if running_ipv6; then
|
|
firewall_filtering_policy_common_late ip6tables INPUT "in_${work_name}"
|
|
firewall_filtering_policy_common_late ip6tables OUTPUT "out_${work_name}"
|
|
fi
|
|
fi
|
|
|
|
set_work_function "Applying default policy of ${work_policy} on interface '${work_name}'"
|
|
|
|
rule chain "in_${work_name}" "${inlog[@]}" action ${work_policy} || return 1
|
|
rule reverse chain "out_${work_name}" "${outlog[@]}" action ${work_policy} || return 1
|
|
|
|
pop_namespace
|
|
return 0
|
|
}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# close_router
|
|
# WHY:
|
|
# Finalizes the rules for the last router().
|
|
|
|
close_router() {
|
|
require_work set router || return 1
|
|
|
|
close_all_groups
|
|
|
|
set_work_function "Finilizing router '${work_name}'"
|
|
|
|
pop_flow_inheritance
|
|
|
|
# make sure we have a policy
|
|
test -z "${work_policy}" && work_policy="${DEFAULT_ROUTER_POLICY}"
|
|
local inlog=() outlog=()
|
|
case "${work_policy}" in
|
|
return|RETURN)
|
|
set_work_function "Nothing to be done for policy RETURN of router '${work_name}'"
|
|
pop_namespace
|
|
return 0
|
|
;;
|
|
|
|
accept|ACCEPT)
|
|
;;
|
|
|
|
*)
|
|
inlog=(loglimit "${work_policy/ */} UNMATCHED PASS-${work_name}")
|
|
outlog=(loglimit "${work_policy/ */} UNMATCHED PASS-${work_name}")
|
|
;;
|
|
esac
|
|
|
|
set_work_function "Applying default policy of ${work_policy} on router '${work_name}'"
|
|
|
|
rule chain "in_${work_name}" "${inlog[@]}" action ${work_policy} || return 1
|
|
rule reverse chain "out_${work_name}" "${outlog[@]}" action ${work_policy} || return 1
|
|
|
|
pop_namespace
|
|
return 0
|
|
}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# close_master
|
|
# WHY:
|
|
# Finalizes the rules for the whole firewall.
|
|
# It assumms there is not primary command open.
|
|
|
|
close_master() {
|
|
set_work_function "Finilizing firewall policies"
|
|
|
|
finalize_synproxy
|
|
|
|
if [ ! "${MARKS_SAVERESTORE_STATEFUL_MASK}" = "0x00000000" ]
|
|
then
|
|
set_work_function "Restoring stateful permanent marks"
|
|
|
|
# copy CONNMARK to MARK at the top of mangle, on entry points
|
|
iptables_both -t mangle -I OUTPUT 1 -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --mask ${MARKS_SAVERESTORE_STATEFUL_MASK}
|
|
iptables_both -t mangle -I PREROUTING 1 -m conntrack --ctstate ESTABLISHED,RELATED -j CONNMARK --restore-mark --mask ${MARKS_SAVERESTORE_STATEFUL_MASK}
|
|
|
|
set_work_function "Saving stateful permanent marks"
|
|
|
|
# save MARK to CONNMARK at the end of mangle, on exit points
|
|
iptables_both -t mangle -A INPUT -m conntrack --ctstate NEW -j CONNMARK --save-mark --mask ${MARKS_SAVERESTORE_STATEFUL_MASK}
|
|
iptables_both -t mangle -A POSTROUTING -m conntrack --ctstate NEW -j CONNMARK --save-mark --mask ${MARKS_SAVERESTORE_STATEFUL_MASK}
|
|
fi
|
|
|
|
if [ ! "${MARKS_SAVERESTORE_STATELESS_MASK}" = "0x00000000" ]
|
|
then
|
|
set_work_function "Restoring stateless permanent marks"
|
|
|
|
# copy CONNMARK to MARK at the top of mangle, on entry points
|
|
iptables_both -t mangle -I OUTPUT 1 -j CONNMARK --restore-mark --mask ${MARKS_SAVERESTORE_STATELESS_MASK}
|
|
iptables_both -t mangle -I PREROUTING 1 -j CONNMARK --restore-mark --mask ${MARKS_SAVERESTORE_STATELESS_MASK}
|
|
|
|
set_work_function "Saving stateless permanent marks"
|
|
|
|
# save MARK to CONNMARK at the end of mangle, on exit points
|
|
iptables_both -t mangle -A INPUT -j CONNMARK --save-mark --mask ${MARKS_SAVERESTORE_STATELESS_MASK}
|
|
iptables_both -t mangle -A POSTROUTING -j CONNMARK --save-mark --mask ${MARKS_SAVERESTORE_STATELESS_MASK}
|
|
fi
|
|
|
|
set_work_function "Apply polices to drop orphan or invalid packets on INPUT/OUTPUT"
|
|
|
|
# Insert session cleanup rules here, after user rules are processed
|
|
# NB that the forward chain is updated along with firewall_filtering_policy_common
|
|
# since they may not be applied in the policy chain
|
|
if [ ${ENABLE_IPV4} -eq 1 ]
|
|
then
|
|
firewall_filtering_policy_common_late iptables INPUT INPUT
|
|
firewall_filtering_policy_common_late iptables OUTPUT OUTPUT
|
|
fi
|
|
if [ ${ENABLE_IPV6} -eq 1 ]
|
|
then
|
|
firewall_filtering_policy_common_late ip6tables INPUT INPUT
|
|
firewall_filtering_policy_common_late ip6tables OUTPUT OUTPUT
|
|
fi
|
|
|
|
set_work_function "Matching all ICMP related packets to the ESTABLISHED connections"
|
|
|
|
if [ ${ENABLE_IPV4} -eq 1 ]
|
|
then
|
|
iptables -A INPUT -m conntrack --ctstate RELATED -p icmp -j ACCEPT
|
|
iptables -A OUTPUT -m conntrack --ctstate RELATED -p icmp -j ACCEPT
|
|
iptables -A FORWARD -m conntrack --ctstate RELATED -p icmp -j ACCEPT
|
|
fi
|
|
if [ ${ENABLE_IPV6} -eq 1 ]
|
|
then
|
|
ip6tables -A INPUT -m conntrack --ctstate RELATED -p icmpv6 -j ACCEPT
|
|
ip6tables -A OUTPUT -m conntrack --ctstate RELATED -p icmpv6 -j ACCEPT
|
|
ip6tables -A FORWARD -m conntrack --ctstate RELATED -p icmpv6 -j ACCEPT
|
|
fi
|
|
|
|
set_work_function "Accepting TCP-RESET at the end of the firewall."
|
|
rule chain "OUTPUT" state RELATED proto tcp custom '--tcp-flags ALL ACK,RST' action ACCEPT || return 1
|
|
rule chain "FORWARD" state RELATED proto tcp custom '--tcp-flags ALL ACK,RST' action ACCEPT || return 1
|
|
|
|
# TEST
|
|
# this should not match anything
|
|
#iptables -A INPUT -m conntrack --ctstate RELATED -j ACCEPT
|
|
#iptables -A OUTPUT -m conntrack --ctstate RELATED -j ACCEPT
|
|
#iptables -A FORWARD -m conntrack --ctstate RELATED -j ACCEPT
|
|
|
|
set_work_function "Setting default unmatched policy (options: UNMATCHED_INPUT_POLICY UNMATCHED_OUTPUT_POLICY UNMATCHED_ROUTER_POLICY)"
|
|
rule chain INPUT loglimit "${UNMATCHED_INPUT_POLICY/ */} UNMATCHED IN-unknown" action ${UNMATCHED_INPUT_POLICY} || return 1
|
|
rule chain OUTPUT loglimit "${UNMATCHED_OUTPUT_POLICY/ */} UNMATCHED OUT-unknown" action ${UNMATCHED_OUTPUT_POLICY} || return 1
|
|
rule chain FORWARD loglimit "${UNMATCHED_ROUTER_POLICY/ */} UNMATCHED PASS-unknown" action ${UNMATCHED_ROUTER_POLICY} || return 1
|
|
|
|
# ---------------------------------------------------------------------
|
|
# execute all postprocessing commands for this firewall
|
|
|
|
if [ ${FIREHOL_ROUTING} -eq 1 ]; then postprocess -warn ${SYSCTL_CMD} -w "net.ipv4.ip_forward=1" || return 1; fi
|
|
|
|
# netfilter delays the creation of files under /proc/sys/net/netfilter
|
|
# so, we wait for them to be available
|
|
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=864341#30
|
|
postprocess -warn postprocess_wait_netfilter
|
|
|
|
if [ "${FIREHOL_CONNTRACK_HELPERS_ASSIGNMENT}" = "kernel" ]
|
|
then
|
|
if [ -f /proc/sys/net/netfilter/nf_conntrack_helper ]
|
|
then
|
|
postprocess -warn ${SYSCTL_CMD} -w "net.netfilter.nf_conntrack_helper=1" || return 1
|
|
fi
|
|
else
|
|
postprocess -warn ${SYSCTL_CMD} -w "net.netfilter.nf_conntrack_helper=0" || return 1
|
|
fi
|
|
|
|
if [ ! -z "${FIREHOL_CONNTRACK_LOOSE_MATCHING}" ]; then postprocess -warn ${SYSCTL_CMD} -w "net.netfilter.nf_conntrack_tcp_loose=${FIREHOL_CONNTRACK_LOOSE_MATCHING}" || return 1; fi
|
|
if [ ! -z "${FIREHOL_TCP_SYN_COOKIES}" ]; then postprocess -warn ${SYSCTL_CMD} -w "net.ipv4.tcp_syncookies=${FIREHOL_TCP_SYN_COOKIES}" || return 1; fi
|
|
if [ ! -z "${FIREHOL_TCP_TIMESTAMPS}" ]; then postprocess -warn ${SYSCTL_CMD} -w "net.ipv4.tcp_timestamps=${FIREHOL_TCP_TIMESTAMPS}" || return 1; fi
|
|
if [ ! -z "${FIREHOL_CONNTRACK_MAX}" ]; then postprocess -warn ${SYSCTL_CMD} -w "net.netfilter.nf_conntrack_max=${FIREHOL_CONNTRACK_MAX}" || return 1; fi
|
|
if [ ! -z "${FIREHOL_CONNTRACK_HASHSIZE}" ]; then postprocess -warn postprocess_echo_to ${FIREHOL_CONNTRACK_HASHSIZE} /sys/module/nf_conntrack/parameters/hashsize || return 1; fi
|
|
|
|
for m in "${!FIREHOL_NFACCT[@]}"
|
|
do
|
|
# -ne here because nfacct will generate an error
|
|
# if the object already exists.
|
|
postprocess -ne ${NFACCT_CMD} add "${m}" || return 1
|
|
done
|
|
|
|
# FIXME
|
|
# ipsets_apply_all() should generate post-processing commands
|
|
# which should be added here
|
|
|
|
return 0
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# flow inheritance
|
|
|
|
# this system keeps track of inface, outface, src, dst for all branching
|
|
# done by interfaces, routers and groups, thus providing a stack of all the
|
|
# required branches (the path) the flow of packets takes in the filter table.
|
|
|
|
# we use this flow inheritance to re-construct the path in table 'raw' when
|
|
# FIREHOL_CONNTRACK_HELPERS_ASSIGNMENT=firehol and a helper is required to be configured.
|
|
# So in table 'raw' we have only the part of the path that is really required.
|
|
|
|
FIREHOL_FLOW_INHERITANCE_STACK_IN=()
|
|
FIREHOL_FLOW_INHERITANCE_STACK_OUT=()
|
|
|
|
reconstruct_flow_inheritance() {
|
|
test ! "${FIREHOL_CONNTRACK_HELPERS_ASSIGNMENT}" = "firehol" && return 0
|
|
|
|
local type="${1}" table="${2}" chain="${3}" overwrite=() stack=() x= ns= new_chain=
|
|
shift 3
|
|
|
|
# we expect at $* optional rule parameters to be appended
|
|
# this is required to overwrite for example the outface in raw table
|
|
overwrite=("${@}")
|
|
|
|
if [ "${type}" = "in" ]
|
|
then
|
|
stack=("${FIREHOL_FLOW_INHERITANCE_STACK_IN[@]}")
|
|
else
|
|
stack=("${FIREHOL_FLOW_INHERITANCE_STACK_OUT[@]}")
|
|
fi
|
|
|
|
for x in "${!stack[@]}"
|
|
do
|
|
set -- ${stack[$x]}
|
|
ns="${1}"
|
|
new_chain="${2}"
|
|
shift 2
|
|
|
|
set_work_function "Reconstruction check of '${ns}' table '${table}'' chain '${new_chain}' with options: ${@}"
|
|
|
|
push_namespace "${ns}"
|
|
chain_exists "${table}" "${new_chain}"
|
|
if [ $? -eq 0 ]
|
|
then
|
|
set_work_function "Reconstructing chain '${new_chain}' in ${table}.${chain} with options: ${@} nosoftwarnings ${overwrite[@]}"
|
|
|
|
create_chain ${table} "${new_chain}" "${chain}" "${@}" nosoftwarnings "${overwrite[@]}" || return 1
|
|
chain="${new_chain}"
|
|
else
|
|
set_work_function "Chain '${new_chain}' already exists under ${table}.${chain}"
|
|
chain="${new_chain}"
|
|
fi
|
|
pop_namespace
|
|
done
|
|
}
|
|
|
|
push_flow_inheritance() {
|
|
test ! "${FIREHOL_CONNTRACK_HELPERS_ASSIGNMENT}" = "firehol" && return 0
|
|
|
|
local type="${1}" chain="${2}" infacenot="${3}" inface="${4}" outfacenot="${5}" outface="${6}" srcnot="${7}" src4="${8}" src6="${9}" dstnot="${10}" dst4="${11}" dst6="${12}"
|
|
|
|
if [ "${type}" = "in" ]
|
|
then
|
|
FIREHOL_FLOW_INHERITANCE_STACK_IN=("${FIREHOL_FLOW_INHERITANCE_STACK_IN[@]}" "${FIREHOL_NS_CURR} ${chain} inface ${infacenot} ${inface// /,} outface ${outfacenot} ${outface// /,} src4 ${srcnot} ${src4// /,} dst4 ${dstnot} ${dst4// /,} src6 ${srcnot} ${src6// /,} dst6 ${dstnot} ${dst6// /,}")
|
|
# declare >&2 -p FIREHOL_FLOW_INHERITANCE_STACK_IN
|
|
else
|
|
FIREHOL_FLOW_INHERITANCE_STACK_OUT=("${FIREHOL_FLOW_INHERITANCE_STACK_OUT[@]}" "${FIREHOL_NS_CURR} ${chain} inface ${infacenot} ${inface// /,} outface ${outfacenot} ${outface// /,} src4 ${srcnot} ${src4// /,} dst4 ${dstnot} ${dst4// /,} src6 ${srcnot} ${src6// /,} dst6 ${dstnot} ${dst6// /,}")
|
|
# declare >&2 -p FIREHOL_FLOW_INHERITANCE_STACK_OUT
|
|
fi
|
|
}
|
|
|
|
pop_flow_inheritance() {
|
|
test ! "${FIREHOL_CONNTRACK_HELPERS_ASSIGNMENT}" = "firehol" && return 0
|
|
|
|
local type="${1}" items_in=${#FIREHOL_FLOW_INHERITANCE_STACK_IN[*]} items_out=${#FIREHOL_FLOW_INHERITANCE_STACK_OUT[*]}
|
|
|
|
items_in=$[ items_in - 1 ]
|
|
unset FIREHOL_FLOW_INHERITANCE_STACK_IN[$items_in]
|
|
|
|
items_out=$[ items_out - 1 ]
|
|
unset FIREHOL_FLOW_INHERITANCE_STACK_OUT[$items_out]
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# groups
|
|
|
|
# groups are used to group services together, with the same optional rule
|
|
# parameters.
|
|
# all optional rule parameters given to group are checked only once
|
|
|
|
FIREHOL_GROUP_COUNTER=0
|
|
FIREHOL_GROUP_DEPTH=0
|
|
FIREHOL_GROUP_STACK=()
|
|
|
|
group4() { ipv4 group "${@}"; }
|
|
group6() { ipv6 group "${@}"; }
|
|
group46() { both group "${@}"; }
|
|
group() {
|
|
work_realcmd_primary ${FUNCNAME} "${@}"
|
|
|
|
require_work set any || return 1
|
|
|
|
local ipv="${FIREHOL_NS_CURR}"
|
|
if [ "z${1}" = "z-ns" ]
|
|
then
|
|
ipv="${2}"
|
|
shift 2
|
|
fi
|
|
|
|
local type="${1}"; shift
|
|
|
|
case $type in
|
|
with|start|begin)
|
|
push_namespace "${ipv}"
|
|
# increase the counter
|
|
FIREHOL_GROUP_COUNTER=$[FIREHOL_GROUP_COUNTER + 1]
|
|
|
|
set_work_function "Rules for new group No ${FIREHOL_GROUP_COUNTER}, under '${work_name}'"
|
|
|
|
# put the current name in the stack
|
|
FIREHOL_GROUP_STACK[$FIREHOL_GROUP_DEPTH]=${work_name}
|
|
FIREHOL_GROUP_DEPTH=$[FIREHOL_GROUP_DEPTH + 1]
|
|
|
|
# name for the new chain
|
|
mychain="group${FIREHOL_GROUP_COUNTER}"
|
|
|
|
# create the new chain in filter
|
|
create_chain filter "in_${mychain}" "in_${work_name}" push_flow_inheritance in in "${@}" || return 1
|
|
create_chain filter "out_${mychain}" "out_${work_name}" push_flow_inheritance out out reverse "${@}" || return 1
|
|
|
|
# set a new name for new rules
|
|
work_name=${mychain}
|
|
;;
|
|
|
|
end|stop|close)
|
|
if [ ${FIREHOL_GROUP_DEPTH} -eq 0 ]
|
|
then
|
|
error "There is no group open to close."
|
|
return 1
|
|
fi
|
|
|
|
# pop one name from the stack
|
|
FIREHOL_GROUP_DEPTH=$[FIREHOL_GROUP_DEPTH - 1]
|
|
|
|
pop_flow_inheritance
|
|
|
|
set_work_function "Closing group '${work_name}'. Now working under '${FIREHOL_GROUP_STACK[$FIREHOL_GROUP_DEPTH]}'"
|
|
|
|
work_name=${FIREHOL_GROUP_STACK[$FIREHOL_GROUP_DEPTH]}
|
|
pop_namespace
|
|
;;
|
|
|
|
*)
|
|
error "Statement 'group' requires the first argument to be one of with, start, begin, end, stop, close."
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
return 0
|
|
}
|
|
|
|
close_all_groups() {
|
|
while [ ${FIREHOL_GROUP_DEPTH} -gt 0 ]
|
|
do
|
|
group close || return 1
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# AUTO ACTIONS
|
|
#
|
|
|
|
FIREHOL_AUTO_ACTION_COUNTER=0
|
|
FIREHOL_AUTO_ACTION_LAST=
|
|
declare -A FIREHOL_AUTO_ACTIONS=()
|
|
|
|
create_auto_state_new_action() {
|
|
local table="${1}" action="${2}"
|
|
shift 2
|
|
local name="${action} ${*}" chain
|
|
|
|
# if it already exists, return
|
|
if [ ! -z "${FIREHOL_AUTO_ACTIONS[${name}]}" ]
|
|
then
|
|
FIREHOL_AUTO_ACTION_LAST="${FIREHOL_AUTO_ACTIONS[${name}]}"
|
|
return 0
|
|
fi
|
|
|
|
FIREHOL_AUTO_ACTION_COUNTER=$[FIREHOL_AUTO_ACTION_COUNTER + 1]
|
|
|
|
if [ ${#name} -gt 28 ]
|
|
then
|
|
chain="$(echo "${name:0:24}_ID${FIREHOL_AUTO_ACTION_COUNTER}" | $TR_CMD " /\-.[a-z]" "____[A-Z]")"
|
|
else
|
|
chain="$(echo "${name}" | $TR_CMD " /\-.[a-z]" "____[A-Z]")"
|
|
fi
|
|
|
|
|
|
# register it, to avoid creating it again
|
|
# echo >&2 "NEW AUTO ACTION: ${chain}"
|
|
FIREHOL_AUTO_ACTIONS[${name}]="${chain}"
|
|
FIREHOL_AUTO_ACTION_LAST="${chain}"
|
|
|
|
# the chain does not exist. create it.
|
|
# echo >&2 "CREATING CHAIN: ${chain}"
|
|
create_chain ${table} "${FIREHOL_AUTO_ACTION_LAST}" || return 1
|
|
|
|
# echo >&2 "ADDING NOT-NEW MATCH: ${chain}"
|
|
rule table ${table} chain "${FIREHOL_AUTO_ACTION_LAST}" state not NEW action "${action}"
|
|
# echo >&2 "ADDING NEW MATCH: ${chain}"
|
|
rule table ${table} chain "${FIREHOL_AUTO_ACTION_LAST}" "${@}" action "${action}"
|
|
|
|
return 0
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# rule - the heart of FireHOL - iptables commands generation
|
|
# WHY:
|
|
# This is the function that gives all the magic to FireHOL. Actually it is a
|
|
# wrapper for iptables, producing multiple iptables commands based on its
|
|
# arguments. The rest of FireHOL is simply a "driver" for this function.
|
|
|
|
|
|
# rule_action_param() is a function - part of rule() - to create the final iptables cmd
|
|
# taking into account the "action_param" parameter of the action.
|
|
|
|
# rule_action_param() should only be used within rule() - no other place
|
|
|
|
FIREHOL_CONNLOG_CHAIN_COUNT=0
|
|
declare -A FIREHOL_CONNLOG_TARGETS=()
|
|
|
|
declare -A SMART_REJECT_CREATED=()
|
|
FIREHOL_ACCEPT_CHAIN_COUNT=0
|
|
rule_action_param() {
|
|
# echo >&2 " >>> ${FUNCNAME}: ${*}"
|
|
|
|
local iptables_cmd="${1}" \
|
|
action="${2}" \
|
|
statenot="${3}" \
|
|
state="${4}" \
|
|
table="${5}" \
|
|
connlog="${6}" \
|
|
count=0 val=
|
|
shift 6
|
|
local -a action_param=()
|
|
|
|
# All arguments until the separator are the parameters of the action
|
|
for val in "${@}"
|
|
do
|
|
[ "A${val}" = "A--" ] && break
|
|
|
|
action_param[$count]="${val}"
|
|
((count += 1))
|
|
done
|
|
shift $[count + 1]
|
|
|
|
# If we don't have a separator, generate an error
|
|
if [ ! "A${val}" = "A--" ]
|
|
then
|
|
error "Internal Error, in parsing action_param parameters (${FUNCNAME} '${action}' '${statenot}' '${state}' '${table}' '${action_param[@]}' '${@}')."
|
|
return 1
|
|
fi
|
|
|
|
# prepare the actions
|
|
case "${action}" in
|
|
NOOP)
|
|
# run the statement without action
|
|
$iptables_cmd "${@}"
|
|
return 0
|
|
;;
|
|
|
|
NONE)
|
|
# don't run any statements
|
|
return 0
|
|
;;
|
|
|
|
SMART_REJECT)
|
|
local key="${iptables_cmd}.${table}"
|
|
key=${key// /_}; key=${key//-/_}; key=${key//\//_}
|
|
if [ -z "${SMART_REJECT_CREATED[$key]}" ]
|
|
then
|
|
SMART_REJECT_CREATED[$key]="1"
|
|
$iptables_cmd -t ${table} -N SMART_REJECT
|
|
$iptables_cmd -t ${table} -A SMART_REJECT -p tcp -j REJECT --reject-with tcp-reset
|
|
$iptables_cmd -t ${table} -A SMART_REJECT -j REJECT
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
# do we have a connlog?
|
|
# if yes, create a new chain for the log
|
|
if [ ! -z "${connlog}" -a ! "${action}" = "RETURN" ]
|
|
then
|
|
local connlog_name="${action} ${connlog}" connlog_chain=
|
|
if [ -z "${FIREHOL_CONNLOG_TARGETS[${connlog_name}]}" ]
|
|
then
|
|
FIREHOL_CONNLOG_CHAIN_COUNT=$[FIREHOL_CONNLOG_CHAIN_COUNT + 1]
|
|
connlog_chain="${action:0:23}_CL${FIREHOL_CONNLOG_CHAIN_COUNT}"
|
|
FIREHOL_CONNLOG_TARGETS[${connlog_name}]="${connlog_chain}"
|
|
|
|
create_chain ${table} ${connlog_chain}
|
|
rule table ${table} chain ${connlog_chain} state NEW action LOG "${connlog}"
|
|
else
|
|
connlog_chain="${FIREHOL_CONNLOG_TARGETS[${connlog_name}]}"
|
|
fi
|
|
rule table ${table} chain ${connlog_chain} action ${action}
|
|
action="${connlog_chain}"
|
|
fi
|
|
|
|
# do we have any options?
|
|
if [ ! -z "${action_param[0]}" ]
|
|
then
|
|
# find the options we have
|
|
case "${action_param[0]}" in
|
|
|
|
connlimit|hashlimit)
|
|
# find is this rule matches NEW connections
|
|
local do_jump=0
|
|
if [ -z "${state}" ]
|
|
then
|
|
do_jump=1
|
|
else
|
|
local has_new=$(echo "${state}" | $GREP_CMD -i NEW)
|
|
if [ -z "${statenot}" ]
|
|
then
|
|
test ! -z "${has_new}" && do_jump=1
|
|
else
|
|
test -z "${has_new}" && do_jump=1
|
|
fi
|
|
fi
|
|
|
|
if [ ${do_jump} -eq 1 ]
|
|
then
|
|
create_auto_state_new_action ${table} ${action} "${action_param[@]}"
|
|
action_param=()
|
|
action="${FIREHOL_AUTO_ACTION_LAST}"
|
|
else
|
|
action_param=()
|
|
fi
|
|
;;
|
|
|
|
"limit")
|
|
# limit NEW connections to the specified rate
|
|
local freq="${action_param[1]}" \
|
|
burst="${action_param[2]}" \
|
|
overflow="REJECT"
|
|
|
|
# if we have a custom overflow action, parse it.
|
|
test "${action_param[3]}" = "overflow" && overflow="`echo "${action_param[4]}" | $TR_CMD "a-z" "A-Z"`"
|
|
|
|
# unset the action_param, so that if this rule does not include NEW connections,
|
|
# we will not append anything to the generated iptables statements.
|
|
action_param=()
|
|
|
|
# find is this rule matches NEW connections
|
|
local has_new=`echo "${state}" | $GREP_CMD -i NEW`
|
|
local do_accept_limit=0
|
|
if [ -z "${statenot}" ]
|
|
then
|
|
test ! -z "${has_new}" && do_accept_limit=1
|
|
else
|
|
test -z "${has_new}" && do_accept_limit=1
|
|
fi
|
|
|
|
# we have a match for NEW connections.
|
|
# redirect the traffic to a new chain, which will control
|
|
# the NEW connections while allowing all the other traffic
|
|
# to pass.
|
|
if [ "${do_accept_limit}" = "1" ]
|
|
then
|
|
local accept_limit_chain="`echo "ACC LIM ${freq} ${burst} ${overflow}" | $TR_CMD " /." "___"`"
|
|
|
|
# does the chain we need already exist?
|
|
#if [ ! -f "${FIREHOL_CHAINS_DIR}/${accept_limit_chain}.${iptables_cmd}" ]
|
|
if [ -z "${FIREHOL_CHAINS[${accept_limit_chain}.${iptables_cmd}]}" ]
|
|
then
|
|
# the chain does not exist. create it.
|
|
$iptables_cmd -t ${table} -N "${accept_limit_chain}"
|
|
FIREHOL_CHAINS[${accept_limit_chain}.${iptables_cmd}]="1"
|
|
#$TOUCH_CMD "${FIREHOL_CHAINS_DIR}/${accept_limit_chain}.${iptables_cmd}"
|
|
|
|
# first, if the traffic is not a NEW connection, allow it.
|
|
# doing this first will speed up normal traffic.
|
|
$iptables_cmd -t ${table} -A "${accept_limit_chain}" -m conntrack ! --ctstate NEW -j ${action}
|
|
|
|
# accept NEW connections within the given limits.
|
|
$iptables_cmd -t ${table} -A "${accept_limit_chain}" -m limit --limit "${freq}" --limit-burst "${burst}" -j ${action}
|
|
|
|
# log the overflow NEW connections reaching this step within the new chain
|
|
prepare_iptables_log_arg "LIMIT OVERFLOW" || return 1
|
|
$iptables_cmd -t ${table} -A "${accept_limit_chain}" -m limit --limit "${FIREHOL_LOG_FREQUENCY}" --limit-burst "${FIREHOL_LOG_BURST}" -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${FIREHOL_LOG_IPTABLES_ARG[@]}"
|
|
|
|
# if the overflow is to be rejected is tcp, reject it with TCP-RESET
|
|
if [ "${overflow}" = "REJECT" ]
|
|
then
|
|
$iptables_cmd -t ${table} -A "${accept_limit_chain}" -p tcp -j REJECT --reject-with tcp-reset
|
|
fi
|
|
|
|
# do the specified action on the overflow
|
|
$iptables_cmd -t ${table} -A "${accept_limit_chain}" -j ${overflow}
|
|
fi
|
|
|
|
# send the rule to be generated to this chain
|
|
action=${accept_limit_chain}
|
|
fi
|
|
;;
|
|
|
|
"recent")
|
|
# limit NEW connections to the specified rate
|
|
local name="${action_param[1]}" \
|
|
seconds="${action_param[2]}" \
|
|
hits="${action_param[3]}"
|
|
|
|
# unset the action_param, so that if this rule does not include NEW connections,
|
|
# we will not append anything to the generated iptables statements.
|
|
action_param=()
|
|
|
|
# find is this rule matches NEW connections
|
|
local has_new=`echo "${state}" | $GREP_CMD -i NEW`
|
|
local do_accept_recent=0
|
|
if [ -z "${statenot}" ]
|
|
then
|
|
test ! -z "${has_new}" && do_accept_recent=1
|
|
else
|
|
test -z "${has_new}" && do_accept_recent=1
|
|
fi
|
|
|
|
# we have a match for NEW connections.
|
|
# redirect the traffic to a new chain, which will control
|
|
# the NEW connections while allowing all the other traffic
|
|
# to pass.
|
|
if [ "${do_accept_recent}" = "1" ]
|
|
then
|
|
local accept_recent_chain="`echo "ACC REC $name $seconds $hits" | $TR_CMD " /." "___"`"
|
|
|
|
# does the chain we need already exist?
|
|
#if [ ! -f "${FIREHOL_CHAINS_DIR}/${accept_recent_chain}.${iptables_cmd}" ]
|
|
if [ -z "${FIREHOL_CHAINS[${accept_recent_chain}.${iptables_cmd}]}" ]
|
|
then
|
|
# the chain does not exist. create it.
|
|
$iptables_cmd -t ${table} -N "${accept_recent_chain}"
|
|
FIREHOL_CHAINS[${accept_recent_chain}.${iptables_cmd}]="1"
|
|
#$TOUCH_CMD "${FIREHOL_CHAINS_DIR}/${accept_recent_chain}.${iptables_cmd}"
|
|
|
|
# first, if the traffic is not a NEW connection, allow it.
|
|
# doing this first will speed up normal traffic.
|
|
$iptables_cmd -t ${table} -A "${accept_recent_chain}" -m conntrack ! --ctstate NEW -j ${action}
|
|
|
|
# accept NEW connections within the given limits.
|
|
$iptables_cmd -t ${table} -A "${accept_recent_chain}" -m recent --set --name "${name}"
|
|
|
|
local t1= t2=
|
|
test ! -z $seconds && t1="--seconds ${seconds}"
|
|
test ! -z $hits && t2="--hitcount ${hits}"
|
|
|
|
$iptables_cmd -t ${table} -A "${accept_recent_chain}" -m recent --update ${t1} ${t2} --name "${name}" -j RETURN
|
|
$iptables_cmd -t ${table} -A "${accept_recent_chain}" -j ${action}
|
|
fi
|
|
|
|
# send the rule to be generated to this chain
|
|
action=${accept_recent_chain}
|
|
fi
|
|
;;
|
|
|
|
'knock')
|
|
# the name of the knock
|
|
local name="knock_${action_param[1]}"
|
|
|
|
# unset the action_param, so that if this rule does not include NEW connections,
|
|
# we will not append anything to the generated iptables statements.
|
|
action_param=()
|
|
|
|
# does the knock chain exists?
|
|
#if [ ! -f "${FIREHOL_CHAINS_DIR}/${name}.${iptables_cmd}" ]
|
|
if [ -z "${FIREHOL_CHAINS[${name}.${iptables_cmd}]}" ]
|
|
then
|
|
# the chain does not exist. create it.
|
|
$iptables_cmd -t ${table} -N "${name}"
|
|
FIREHOL_CHAINS[${name}.${iptables_cmd}]="1"
|
|
#$TOUCH_CMD "${FIREHOL_CHAINS_DIR}/${name}.${iptables_cmd}"
|
|
|
|
$iptables_cmd -A "${name}" -m conntrack --ctstate ESTABLISHED -j ${action}
|
|
|
|
# knockd (http://www.zeroflux.org/knock/)
|
|
# will create more rules inside this chain to match NEW packets.
|
|
fi
|
|
|
|
# send the rule to be generated to this knock chain
|
|
action=${name}
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
$iptables_cmd "${@}" -j "${action}" "${action_param[@]}"
|
|
}
|
|
|
|
PROGRAM_SPINNER_SPACES=' '
|
|
PROGRAM_SPINNER_BACKSPACES='\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b'
|
|
PROGRAM_SPINNER_LAST=0
|
|
PROGRAM_SPINNER='|/-\'
|
|
PROGRAM_SPINNER_RUNNING=0
|
|
PROGRAM_SPINNER_PREFIX="iptables rules:"
|
|
spinner()
|
|
{
|
|
local t="${PROGRAM_SPINNER_PREFIX} ${1}"
|
|
printf >&2 "${PROGRAM_SPINNER_BACKSPACES:0:$PROGRAM_SPINNER_LAST}"
|
|
PROGRAM_SPINNER_LAST=$(( (${#t} + 5) * 2 ))
|
|
local temp=${PROGRAM_SPINNER#?}
|
|
printf >&2 "[${t} %c] " "${PROGRAM_SPINNER}"
|
|
PROGRAM_SPINNER=$temp${PROGRAM_SPINNER%"$temp"}
|
|
PROGRAM_SPINNER_RUNNING=1
|
|
}
|
|
|
|
spinner_end() {
|
|
local last=$((PROGRAM_SPINNER_LAST / 2))
|
|
printf >&2 "${PROGRAM_SPINNER_BACKSPACES:0:$PROGRAM_SPINNER_LAST}"
|
|
printf >&2 "${PROGRAM_SPINNER_SPACES:0:$last}"
|
|
printf >&2 "${PROGRAM_SPINNER_BACKSPACES:0:$PROGRAM_SPINNER_LAST}"
|
|
PROGRAM_SPINNER_RUNNING=0
|
|
PROGRAM_SPINNER_LAST=0
|
|
}
|
|
|
|
# generate the LOG action parameters according to current logging mode
|
|
FIREHOL_LOG_IPTABLES_ARG=()
|
|
prepare_iptables_log_arg() {
|
|
case "${FIREHOL_LOG_MODE}" in
|
|
ULOG) FIREHOL_LOG_IPTABLES_ARG=("--ulog-prefix=${FIREHOL_LOG_ESCAPE}${FIREHOL_LOG_PREFIX}${*}:${FIREHOL_LOG_ESCAPE}") ;;
|
|
NFLOG) FIREHOL_LOG_IPTABLES_ARG=("--nflog-prefix=${FIREHOL_LOG_ESCAPE}${FIREHOL_LOG_PREFIX}${*}:${FIREHOL_LOG_ESCAPE}") ;;
|
|
LOG) FIREHOL_LOG_IPTABLES_ARG=("--log-level" "${FIREHOL_LOG_LEVEL}" "--log-prefix=${FIREHOL_LOG_ESCAPE}${FIREHOL_LOG_PREFIX}${*}:${FIREHOL_LOG_ESCAPE}");;
|
|
*) FIREHOL_LOG_IPTABLES_ARG=(); error "Invalid log mode ${FIREHOL_LOG_MODE}"; return 1;;
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
FIREHOL_RULE_POSITIVE_STATEMENTS_GENERATED=0
|
|
rule() {
|
|
# echo >&2 " >>> ${FUNCNAME}: ${*}"
|
|
|
|
# defining these local variables together speeds FireHOL up by 4%
|
|
local failed=0 \
|
|
table= chain= \
|
|
inface=(any) infacenot= outface=(any) outfacenot= \
|
|
physin=(any) physinnot= physout=(any) physoutnot= \
|
|
mac=(any) macnot= \
|
|
src4=(default) src4not= dst4=(default) dst4not= \
|
|
src6=(default) src6not= dst6=(default) dst6not= \
|
|
srctype= srctypenot= dsttype= dsttypenot= \
|
|
sport=(any) sportnot= dport=(any) dportnot= \
|
|
proto=(any) protonot= \
|
|
uid=(any) uidnot= gid=(any) gidnot= \
|
|
mark=(any) marknot= markname= \
|
|
dscp=(any) dscptype= dscpnot= \
|
|
tos=(any) tosnot= \
|
|
log= logtxt= loglevel= \
|
|
limit= burst= connlimit= connlimit_mask= \
|
|
hashlimit= \
|
|
hashlimit_name= hashlimit_type= hashlimit_burst= \
|
|
hashlimit_mode= hashlimit_srcmask= hashlimit_dstmask= \
|
|
hashlimit_htable_size= hashlimit_htable_max= \
|
|
hashlimit_htable_expire= hashlimit_htable_gcinterval= \
|
|
action= state= statenot= \
|
|
failed=0 reverse=0 \
|
|
swi=0 swo=0 \
|
|
custom= \
|
|
accounting= connlog= \
|
|
ipsetnot= ipsetname= ipsetflags= ipsetopts= \
|
|
inout= x= param= not= helper=() helpernot= opt_args=
|
|
|
|
# if set to 1, all owner module options will be ignored
|
|
local noowner=0
|
|
|
|
# if set to 1, all mac options will be ignored
|
|
local nomac=0
|
|
|
|
# if set to 1, MIRROR will be converted to REJECT
|
|
local nomirror=0
|
|
|
|
# if set to 1, log and loglimit are ignored.
|
|
local nolog=0
|
|
|
|
# if set to 1, detection algorithm about overwriting optional rule
|
|
# parameters will take place.
|
|
local softwarnings=1
|
|
|
|
# if set to 1, rule() will may use the main chain
|
|
# for returning back
|
|
local return_if_not_matched=0
|
|
|
|
# set it, in order to be local
|
|
local -a action_param=() tmparray=()
|
|
|
|
# a copy of the protocols that should always be given
|
|
# to iptables when taking the action
|
|
local -a require_protocol_with_action=(any)
|
|
|
|
# if set to 1, we will only process state NEW rules
|
|
local optimal=0
|
|
|
|
local push_flow_inheritance_type= positive_rule_number=
|
|
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
param="${1,,}" # to lowercase
|
|
|
|
not=
|
|
if [ "${2,,}" = "not" -o "${2}" = "!" ]
|
|
then
|
|
not="!"
|
|
shift 2
|
|
else
|
|
shift
|
|
fi
|
|
|
|
case "${param}" in
|
|
or) # this is used to allow multiple exception in statements
|
|
# that support the 'except' keyword
|
|
rule table "${table}" chain "${chain}" action "${action}" "${action_param[@]}" ${opt_args} "${@}"
|
|
break
|
|
;;
|
|
|
|
reverse) opt_args="${opt_args} ${param}"; reverse=1 ;;
|
|
nolog) opt_args="${opt_args} ${param}"; nolog=1 ;;
|
|
noowner) opt_args="${opt_args} ${param}"; noowner=1 ;;
|
|
softwarnings) opt_args="${opt_args} ${param}"; softwarnings=1 ;;
|
|
nosoftwarnings) opt_args="${opt_args} ${param}"; softwarnings=0 ;;
|
|
set_work_inface) opt_args="${opt_args} ${param}"; swi=1 ;;
|
|
set_work_outface) opt_args="${opt_args} ${param}"; swo=1 ;;
|
|
return_if_not_matched) opt_args="${opt_args} ${param}"; return_if_not_matched=1 ;;
|
|
optimal) opt_args="${opt_args} ${param}"; optimal=1 ;;
|
|
accurate) opt_args="${opt_args} ${param}"; optimal=0 ;;
|
|
push_flow_inheritance) opt_args="${opt_args} ${param}"; push_flow_inheritance_type="${1}"; shift ;;
|
|
insert) opt_args="${opt_args} ${param}"; positive_rule_number=1 ;;
|
|
insert_at) opt_args="${opt_args} ${param}"; positive_rule_number="${1}"; shift ;;
|
|
|
|
in) # this is incoming traffic - ignore packet ownership
|
|
opt_args="${opt_args} ${param}"
|
|
inout="in"
|
|
noowner=1
|
|
nomirror=0
|
|
nomac=0
|
|
;;
|
|
|
|
out) # this is outgoing traffic - ignore packet ownership if not in an interface
|
|
opt_args="${opt_args} ${param}"
|
|
inout="out"
|
|
if [ ! "${work_cmd}" = "interface" ]
|
|
then
|
|
noowner=1
|
|
else
|
|
nomirror=1
|
|
fi
|
|
nomac=1
|
|
;;
|
|
|
|
table) test ${softwarnings} -eq 1 -a ! -z "${table}" && softwarning "Overwriting param: ${1} '${chain}' becomes '${1}'"
|
|
table="${1}"
|
|
shift
|
|
;;
|
|
|
|
chain) test ${softwarnings} -eq 1 -a ! -z "${chain}" && softwarning "Overwriting param: ${1} '${chain}' becomes '${1}'"
|
|
chain="${1}"
|
|
shift
|
|
;;
|
|
|
|
inface|outface)
|
|
if [ \( "${param}" = "inface" -a ${reverse} -eq 0 \) -o \( "${param}" = "outface" -a ${reverse} -eq 1 \) ]
|
|
then
|
|
infacenot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${inface[*]}" = "any" && softwarning "Overwriting param: inface '${inface[*]}' becomes '${1}'"
|
|
inface=(${1//,/ })
|
|
[ -z "${infacenot}" -a ${swi} -eq 1 ] && work_inface="${inface[*]}"
|
|
test -z "${inface[*]}" && error "Cannot accept an empty 'inface'." && return 1
|
|
else
|
|
outfacenot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${outface[*]}" = "any" && softwarning "Overwriting param: outface '${outface[*]}' becomes '${1}'"
|
|
outface=(${1//,/ })
|
|
[ -z "${outfacenot}" -a ${swo} -eq 1 ] && work_outface="${outface[*]}"
|
|
test -z "${outface[*]}" && error "Cannot accept an empty 'outface'." && return 1
|
|
fi
|
|
shift
|
|
;;
|
|
|
|
physin|physout)
|
|
if [ \( "${param}" = "physin" -a ${reverse} -eq 0 \) -o \( "${param}" = "physout" -a ${reverse} -eq 1 \) ]
|
|
then
|
|
physinnot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${physin[*]}" = "any" && softwarning "Overwriting param: physin '${physin[*]}' becomes '${1}'"
|
|
physin=(${1//,/ })
|
|
test -z "${physin[*]}" && error "Cannot accept an empty 'physin'." && return 1
|
|
else
|
|
physoutnot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${physout[*]}" = "any" && softwarning "Overwriting param: physout '${physout[*]}' becomes '${1}'"
|
|
physout=(${1//,/ })
|
|
test -z "${physout[*]}" && error "Cannot accept an empty 'physout'." && return 1
|
|
fi
|
|
shift
|
|
;;
|
|
|
|
mac)
|
|
macnot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${mac[*]}" = "any" && softwarning "Overwriting param: mac '${mac[*]}' becomes '${1}'"
|
|
test ${nomac} -eq 0 && mac=(${1//,/ })
|
|
shift
|
|
test -z "${mac[*]}" && error "Cannot accept an empty 'mac'." && return 1
|
|
;;
|
|
|
|
src|src4|src6|dst|dst4|dst6)
|
|
if [ "${param/*4/4}" = "4" ]; then push_namespace ipv4 || return 1;
|
|
elif [ "${param/*6/6}" = "6" ]; then push_namespace ipv6 || return 1;
|
|
else push_namespace "${FIREHOL_NS_CURR}" || return 1; fi
|
|
|
|
if [ \( "${param//src*/src}" = "src" -a ${reverse} -eq 0 \) -o \( "${param/dst*/dst}" = "dst" -a ${reverse} -eq 1 \) ]
|
|
then
|
|
if running_ipv4
|
|
then
|
|
src4not="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${src4[*]}" = "default" && softwarning "Overwriting param: src4 '${src4[*]}' becomes '${1}'"
|
|
src4=(${1//,/ })
|
|
test -z "${src4[*]}" && error "Cannot accept an empty 'src4'." && return 1
|
|
fi
|
|
if running_ipv6
|
|
then
|
|
src6not="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${src6[*]}" = "default" && softwarning "Overwriting param: src6 '${src6[*]}' becomes '${1}'"
|
|
src6=(${1//,/ })
|
|
test -z "${src6[*]}" && error "Cannot accept an empty 'src6'." && return 1
|
|
fi
|
|
else
|
|
if running_ipv4
|
|
then
|
|
dst4not="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${dst4[*]}" = "default" && softwarning "Overwriting param: dst4 '${dst4[*]}' becomes '${1}'"
|
|
dst4=(${1//,/ })
|
|
test -z "${dst4[*]}" && error "Cannot accept an empty 'dst4'." && return 1
|
|
fi
|
|
if running_ipv6
|
|
then
|
|
dst6not="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${dst6[*]}" = "default" && softwarning "Overwriting param: dst6 '${dst6[*]}' becomes '${1}'"
|
|
dst6=(${1//,/ })
|
|
test -z "${dst6[*]}" && error "Cannot accept an empty 'dst6'." && return 1
|
|
fi
|
|
fi
|
|
shift
|
|
pop_namespace
|
|
;;
|
|
|
|
srctype|dsttype)
|
|
if [ \( "${param}" = "srctype" -a ${reverse} -eq 0 \) -o \( "${param}" = "dsttype" -a ${reverse} -eq 1 \) ]
|
|
then
|
|
srctypenot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! -z "${srctype[*]}" && softwarning "Overwriting param: srctype '${srctype[*]}' becomes '${1}'"
|
|
tmparray=( ${1^^} ); srctype="${tmparray[*]}"; srctype="${srctype// /,}"
|
|
else
|
|
dsttypenot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! -z "${dsttype}" && softwarning "Overwriting param: dsttype '${dsttype}' becomes '${1}'"
|
|
tmparray=( ${1^^} ); dsttype="${tmparray[*]}"; dsttype="${dsttype// /,}"
|
|
fi
|
|
shift
|
|
;;
|
|
|
|
sport|dport)
|
|
if [ \( "${param}" = "sport" -a ${reverse} -eq 0 \) -o \( "${param}" = "dport" -a ${reverse} -eq 1 \) ]
|
|
then
|
|
sportnot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${sport[*]}" = "any" && softwarning "Overwriting param: sport '${sport[*]}' becomes '${1}'"
|
|
sport=(${1//,/ })
|
|
test -z "${sport[*]}" && error "Cannot accept an empty 'sport'." && return 1
|
|
else
|
|
dportnot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${dport[*]}" = "any" && softwarning "Overwriting param: dport '${dport[*]}' becomes '${1}'"
|
|
dport=(${1//,/ })
|
|
test -z "${dport[*]}" && error "Cannot accept an empty 'dport'." && return 1
|
|
fi
|
|
shift
|
|
;;
|
|
|
|
proto|protocol)
|
|
protonot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${proto[*]}" = "any" && softwarning "Overwriting param: proto '${proto[*]}' becomes '${1}'"
|
|
proto=(${1//,/ })
|
|
shift
|
|
test -z "${proto[*]}" && error "Cannot accept an empty 'proto'." && return 1
|
|
;;
|
|
|
|
custommark)
|
|
marknot="${not}"
|
|
markname="${1}"; shift
|
|
test ${softwarnings} -eq 1 -a ! "${mark[*]}" = "any" && softwarning "Overwriting param: mark '${mark[*]}' becomes ${markname} '${1}'"
|
|
mark=
|
|
for x in ${1//,/ }
|
|
do
|
|
mark=("${mark[@]}" "$(mark_value $markname ${x})")
|
|
done
|
|
test -z "${mark[*]}" && error "Cannot accept an empty 'mark'." && return 1
|
|
shift
|
|
;;
|
|
|
|
mark)
|
|
marknot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${mark[*]}" = "any" && softwarning "Overwriting param: mark '${mark[*]}' becomes usermark '${1}'"
|
|
mark=
|
|
for x in ${1//,/ }
|
|
do
|
|
mark=("${mark[@]}" "$(mark_value usermark ${x})")
|
|
done
|
|
test -z "${mark[*]}" && error "Cannot accept an empty 'mark'." && return 1
|
|
shift
|
|
;;
|
|
|
|
connmark)
|
|
marknot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${mark[*]}" = "any" && softwarning "Overwriting param: mark '${mark[*]}' becomes connmark '${1}'"
|
|
mark=
|
|
for x in ${1//,/ }
|
|
do
|
|
mark=("${mark[@]}" "$(mark_value connmark ${x})")
|
|
done
|
|
test -z "${mark[*]}" && error "Cannot accept an empty 'mark'." && return 1
|
|
shift
|
|
;;
|
|
|
|
rawmark)
|
|
marknot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${mark[*]}" = "any" && softwarning "Overwriting param: mark '${mark[*]}' becomes '${1}'"
|
|
mark=(${1//,/ })
|
|
test -z "${mark[*]}" && error "Cannot accept an empty 'mark'." && return 1
|
|
shift
|
|
;;
|
|
|
|
tos)
|
|
tosnot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${tos[*]}" = "any" && softwarning "Overwriting param: tos '${tos[*]}' becomes '${1}'"
|
|
tos=(${1//,/ })
|
|
test -z "${tos[*]}" && error "Cannot accept an empty 'tos'." && return 1
|
|
shift
|
|
;;
|
|
|
|
dscp)
|
|
dscpnot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${dscp[*]}" = "any" && softwarning "Overwriting param: dscp '${dscp[*]}' becomes '${1}'"
|
|
dscp=(${1//,/ })
|
|
shift
|
|
if [ "${dscp[*]}" = "class" ]
|
|
then
|
|
dscptype="-class"
|
|
dscp=(${1//,/ })
|
|
shift
|
|
fi
|
|
test -z "${dscp[*]}" && error "Cannot accept an empty 'dscp'." && return 1
|
|
;;
|
|
|
|
state)
|
|
statenot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! -z "${state}" && softwarning "Overwriting param: state '${state}' becomes '${1}'"
|
|
state="${1^^}"
|
|
shift
|
|
;;
|
|
|
|
user|uid)
|
|
uidnot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${uid[*]}" = "any" && softwarning "Overwriting param: uid '${uid[*]}' becomes '${1}'"
|
|
uid=(${1//,/ })
|
|
test -z "${uid[*]}" && error "Cannot accept an empty 'uid'." && return 1
|
|
shift
|
|
;;
|
|
|
|
group|gid)
|
|
gidnot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! "${gid[*]}" = "any" && softwarning "Overwriting param: gid '${gid[*]}' becomes '${1}'"
|
|
gid=(${1//,/ })
|
|
test -z "${gid[*]}" && error "Cannot accept an empty 'gid'." && return 1
|
|
shift
|
|
;;
|
|
|
|
custom)
|
|
test ${softwarnings} -eq 1 -a ! -z "${not}" && softwarning "Cannot negate a custom match. 'not' ignored."
|
|
test ${softwarnings} -eq 1 -a ! -z "${custom}" && softwarning "Overwriting param: custom '${custom}' becomes '${1}'"
|
|
custom="${1}"
|
|
shift
|
|
;;
|
|
|
|
customin|custom-in)
|
|
test ${softwarnings} -eq 1 -a ! -z "${not}" && softwarning "Cannot negate a custom match. 'not' ignored."
|
|
if [ "${inout}" = "in" ]
|
|
then
|
|
test ${softwarnings} -eq 1 -a ! -z "${custom}" && softwarning "Overwriting param: custom '${custom}' becomes '${1}'"
|
|
custom="${1}"
|
|
fi
|
|
shift
|
|
;;
|
|
|
|
customout|custom-out)
|
|
test ${softwarnings} -eq 1 -a ! -z "${not}" && softwarning "Cannot negate a custom match. 'not' ignored."
|
|
if [ "${inout}" = "out" ]
|
|
then
|
|
test ${softwarnings} -eq 1 -a ! -z "${custom}" && softwarning "Overwriting param: custom '${custom}' becomes '${1}'"
|
|
custom="${1}"
|
|
fi
|
|
shift
|
|
;;
|
|
|
|
helper)
|
|
helpernot="${not}"
|
|
test ${softwarnings} -eq 1 -a ! -z "${helper[*]}" && softwarning "Overwriting param: helper '${helper[*]}' becomes '${1}'"
|
|
helper=(${1//,/ })
|
|
shift
|
|
;;
|
|
|
|
connlog)
|
|
test ${softwarnings} -eq 1 -a ! -z "${not}" && softwarning "Cannot negate a connlog. 'not' ignored."
|
|
connlog="${1}"
|
|
shift
|
|
;;
|
|
|
|
log)
|
|
test ${softwarnings} -eq 1 -a ! -z "${not}" && softwarning "Cannot negate a log. 'not' ignored."
|
|
if [ ${nolog} -eq 0 ]
|
|
then
|
|
test ${softwarnings} -eq 1 -a ! -z "${log}" && softwarning "Overwriting param: log '${log}/${logtxt}' becomes 'normal/${1}'"
|
|
log=normal
|
|
logtxt="${1}"
|
|
fi
|
|
shift
|
|
if [ "${1}" = "level" ]
|
|
then
|
|
loglevel="${2}"
|
|
shift 2
|
|
else
|
|
loglevel="${FIREHOL_LOG_LEVEL}"
|
|
fi
|
|
;;
|
|
|
|
loglimit)
|
|
test ${softwarnings} -eq 1 -a ! -z "${not}" && softwarning "Cannot negate a log. 'not' ignored."
|
|
if [ ${nolog} -eq 0 ]
|
|
then
|
|
test ${softwarnings} -eq 1 -a ! -z "${log}" && softwarning "Overwriting param: log '${log}/${logtxt}' becomes 'limit/${1}'"
|
|
log=limit
|
|
logtxt="${1}"
|
|
fi
|
|
shift
|
|
if [ "${1}" = "level" ]
|
|
then
|
|
loglevel="${2}"
|
|
shift 2
|
|
else
|
|
loglevel="${FIREHOL_LOG_LEVEL}"
|
|
fi
|
|
;;
|
|
|
|
limit)
|
|
test ${softwarnings} -eq 1 -a ! -z "${not}" && softwarning "Cannot negate a limit. 'not' ignored."
|
|
test ${softwarnings} -eq 1 -a ! -z "${limit}" && softwarning "Overwriting param: limit '${limit}' becomes '${1}'"
|
|
limit="${1}"
|
|
burst="${2}"
|
|
shift 2
|
|
;;
|
|
|
|
connlimit|iplimit)
|
|
test ${softwarnings} -eq 1 -a ! -z "${not}" && softwarning "Cannot negate a connlimit. 'not' ignored."
|
|
test ${softwarnings} -eq 1 -a ! -z "${connlimit}" && softwarning "Overwriting param: connlimit '${connlimit}' becomes '${1}'"
|
|
connlimit_type=
|
|
connlimit_addr="saddr"
|
|
connlimit_mask=
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
if [ "${1}" = "upto" ]
|
|
then
|
|
connlimit_type="upto"
|
|
connlimit="${2}"
|
|
shift 2
|
|
continue
|
|
elif [ "${1}" = "above" ]
|
|
then
|
|
connlimit_type="above"
|
|
connlimit="${2}"
|
|
shift 2
|
|
continue
|
|
elif [ "${1}" = "mask" ]
|
|
then
|
|
connlimit_mask="${2}"
|
|
shift 2
|
|
continue
|
|
elif [ "${1}" = "saddr" ]
|
|
then
|
|
connlimit_addr="saddr"
|
|
shift
|
|
continue
|
|
elif [ "${1}" = "daddr" ]
|
|
then
|
|
connlimit_addr="daddr"
|
|
shift
|
|
continue
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
if [ -z "${connlimit}" ]
|
|
then
|
|
error "connlimit is not set. Use: connlimit upto|above X [mask M] [saddr|daddr]"
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
hashlimit)
|
|
test ${softwarnings} -eq 1 -a ! -z "${not}" && softwarning "Cannot negate a hashlimit. 'not' ignored."
|
|
test ${softwarnings} -eq 1 -a ! -z "${hashlimit}" && softwarning "Overwriting param: hashlimit '${hashlimit}' becomes '${1}'"
|
|
hashlimit_name="${1}"; shift
|
|
hashlimit_type=
|
|
hashlimit_burst=
|
|
hashlimit_mode="srcip,dstport"
|
|
hashlimit_srcmask=
|
|
hashlimit_dstmask=
|
|
hashlimit_htable_size=
|
|
hashlimit_htable_max=
|
|
hashlimit_htable_expire=
|
|
hashlimit_htable_gcinterval=
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
if [ "${1}" = "upto" ]
|
|
then
|
|
hashlimit_type="upto"
|
|
hashlimit="${2}"
|
|
shift 2
|
|
continue
|
|
elif [ "${1}" = "above" ]
|
|
then
|
|
hashlimit_type="above"
|
|
hashlimit="${2}"
|
|
shift 2
|
|
continue
|
|
elif [ "${1}" = "mode" ]
|
|
then
|
|
hashlimit_mode="${2}"
|
|
shift 2
|
|
continue
|
|
elif [ "${1}" = "burst" ]
|
|
then
|
|
hashlimit_burst="${2}"
|
|
shift 2
|
|
continue
|
|
elif [ "${1}" = "srcmask" ]
|
|
then
|
|
hashlimit_srcmask="${2}"
|
|
shift 2
|
|
continue
|
|
elif [ "${1}" = "dstmask" ]
|
|
then
|
|
hashlimit_dstmask="${2}"
|
|
shift 2
|
|
continue
|
|
elif [ "${1}" = "htable_size" -o "${1}" = "htable-size" ]
|
|
then
|
|
hashlimit_htable_size="${2}"
|
|
shift 2
|
|
continue
|
|
elif [ "${1}" = "htable_max" -o "${1}" = "htable-max" ]
|
|
then
|
|
hashlimit_htable_max="${2}"
|
|
shift 2
|
|
continue
|
|
elif [ "${1}" = "htable_expire" -o "${1}" = "htable-expire" ]
|
|
then
|
|
hashlimit_htable_expire="${2}"
|
|
shift 2
|
|
continue
|
|
elif [ "${1}" = "htable_gcinterval" -o "${1}" = "htable-gcinterval" ]
|
|
then
|
|
hashlimit_htable_gcinterval="${2}"
|
|
shift 2
|
|
continue
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
if [ -z "${hashlimit}" ]
|
|
then
|
|
error "hashlimit is not set. Use: hashlimit name upto|above amount/unit [burst amount] [mode {srcip|srcport|dstip|dstport},...] [srcmask prefix] [dstmask prefix] [htable-size buckets] [htable-max entries] [htable-expire msec] [htable-gcinterval msec]"
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
acct|accounting)
|
|
test ${softwarnings} -eq 1 -a ! -z "${not}" && softwarning "Cannot negate an accounting. 'not' ignored."
|
|
if [ ${ENABLE_ACCOUNTING} -eq 1 ]
|
|
then
|
|
accounting="${1}"
|
|
FIREHOL_NFACCT[${accounting}]="1"
|
|
elif [ ${ACCOUNTING_WARNING} -eq 1 ]
|
|
then
|
|
softwarning "Accounting is requested, but accounting is disabled. Is nfacct installed?"
|
|
ACCOUNTING_WARNING=0
|
|
fi
|
|
shift
|
|
;;
|
|
|
|
ipset)
|
|
ipsetnot="${not}"
|
|
ipsetname="${1}"
|
|
ipsetflags="${2}"
|
|
shift 2
|
|
ipsetopts=
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1}" in
|
|
options)
|
|
ipsetopts="${ipsetopts} ${2}"
|
|
shift 2
|
|
;;
|
|
|
|
no-counters)
|
|
ipsetopts="${ipsetopts} ! --update-counters ! --update-subcounters"
|
|
shift
|
|
;;
|
|
|
|
bytes-above|bytes-gt)
|
|
ipsetopts="${ipsetopts} --bytes-gt ${2}"
|
|
shift 2
|
|
;;
|
|
|
|
bytes|bytes-eq)
|
|
ipsetopts="${ipsetopts} --bytes-eq ${2}"
|
|
shift 2
|
|
;;
|
|
|
|
bytes-different-than|bytes-not-eq)
|
|
ipsetopts="${ipsetopts} ! --bytes-eq ${2}"
|
|
shift 2
|
|
;;
|
|
|
|
bytes-below|bytes-lt)
|
|
ipsetopts="${ipsetopts} --bytes-lt ${2}"
|
|
shift 2
|
|
;;
|
|
|
|
packets-above|packets-gt)
|
|
ipsetopts="${ipsetopts} --packets-gt ${2}"
|
|
shift 2
|
|
;;
|
|
|
|
packets|packets-eq)
|
|
ipsetopts="${ipsetopts} --packets-eq ${2}"
|
|
shift 2
|
|
;;
|
|
|
|
packets-different-than|packets-not-eq)
|
|
ipsetopts="${ipsetopts} ! --packets-eq ${2}"
|
|
shift 2
|
|
;;
|
|
|
|
packets-below|packets-lt)
|
|
ipsetopts="${ipsetopts} --packets-lt ${2}"
|
|
shift 2
|
|
;;
|
|
|
|
*) break
|
|
;;
|
|
esac
|
|
done
|
|
;;
|
|
|
|
action)
|
|
test ${softwarnings} -eq 1 -a ! -z "${action}" && softwarning "Overwriting param: action '${action}' becomes '${2}'"
|
|
test ${softwarnings} -eq 1 -a ! -z "${not}" && softwarning "Cannot negate an action. 'not' ignored."
|
|
action="${1}"
|
|
shift
|
|
|
|
action_param=()
|
|
local action_is_chain=0
|
|
case "${action^^}" in
|
|
|
|
DENY) action="DROP" ;;
|
|
DROP) action="DROP" ;;
|
|
RETURN) action="RETURN" ;;
|
|
NONE) action="NONE" ;;
|
|
TARPIT) action="TARPIT" ;;
|
|
ACCEPT) action="ACCEPT" ;;
|
|
|
|
LOG)
|
|
action="${FIREHOL_LOG_MODE}"
|
|
prepare_iptables_log_arg "${1}"
|
|
action_param=("${FIREHOL_LOG_IPTABLES_ARG[@]}")
|
|
shift
|
|
;;
|
|
|
|
CT) action="CT"
|
|
if [ ! "${table}" = "raw" ]
|
|
then
|
|
error "${action} must on a the 'raw' table but table ${table} is given."
|
|
return 1
|
|
fi
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1}" in
|
|
helper|--helper) action_param=("${action_param[@]}" "--helper" "${2}"); shift 2 ;;
|
|
notrack|--notrack) action_param=("${action_param[@]}" "--notrack"); shift ;;
|
|
*) break ;;
|
|
esac
|
|
done
|
|
if [ -z "${action_param[*]}" ]
|
|
then
|
|
error "${action} cannot work without any arguments."
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
SYNPROXY)
|
|
action="SYNPROXY"
|
|
if [ ! "${proto[*]}" = "tcp" ]
|
|
then
|
|
error "SYNPROXY cannot only be used with TCP, but proto is ${proto[*]}."
|
|
return 1
|
|
fi
|
|
require_protocol_with_action=("${proto[@]}")
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1}" in
|
|
sack-perm|--sack-perm) action_param=("${action_param[@]}" "--sack-perm"); shift ;;
|
|
timestamp|--timestamp) action_param=("${action_param[@]}" "--timestamp"); shift ;;
|
|
wscale|--wscale) action_param=("${action_param[@]}" "--wscale" "${2}"); shift 2 ;;
|
|
mss|--mss) action_param=("${action_param[@]}" "--mss" "${2}"); shift 2 ;;
|
|
*) break ;;
|
|
esac
|
|
done
|
|
if [ -z "${action_param[*]}" ]
|
|
then
|
|
error "${action} cannot work without any arguments."
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
REJECT) action="REJECT"
|
|
if [ "${1}" = "with" -o "${1}" = "--reject-with" ]
|
|
then
|
|
action_param=("--reject-with" "${2}")
|
|
shift 2
|
|
else
|
|
action_param=("--reject-with" "auto")
|
|
fi
|
|
;;
|
|
|
|
|
|
MIRROR) action="MIRROR"
|
|
test $nomirror -eq 1 && action="REJECT"
|
|
;;
|
|
|
|
MASQUERADE)
|
|
action="MASQUERADE"
|
|
if [ ! "${table}" = "nat" ]
|
|
then
|
|
error "${action} must on a the 'nat' table but table ${table} is given."
|
|
return 1
|
|
fi
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1}" in
|
|
ports|to-ports|--to-ports)
|
|
action_param=( "${action_param[@]}" "--to-ports" "${2//:/-}" )
|
|
# ports need a protocol: either tcp or udp (or both if unset)
|
|
test "${proto}" = "any" && proto="tcp udp"
|
|
require_protocol_with_action=("${proto[@]}")
|
|
shift 2
|
|
;;
|
|
|
|
random|--random)
|
|
action_param=( "${action_param[@]}" "--random" )
|
|
shift
|
|
;;
|
|
|
|
*) break
|
|
;;
|
|
esac
|
|
done
|
|
;;
|
|
|
|
SNAT) action="SNAT"
|
|
if [ ! "${table}" = "nat" ]
|
|
then
|
|
error "${action} must on a the 'nat' table but table ${table} is given."
|
|
return 1
|
|
fi
|
|
local hasto=0
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1}" in
|
|
to|to-source|--to-source)
|
|
action_param=( "${action_param[@]}" "--to-source" "${2}" )
|
|
# ports need a protocol: either tcp or udp (or both if unset)
|
|
if [[ "${2}" =~ ":" ]]
|
|
then
|
|
[ "${proto}" = "any" ] && proto="tcp udp"
|
|
require_protocol_with_action=("${proto[@]}")
|
|
fi
|
|
hasto=1
|
|
shift 2
|
|
;;
|
|
|
|
random|--random)
|
|
action_param=( "${action_param[@]}" "--random" )
|
|
shift
|
|
;;
|
|
|
|
persistent|--persistent)
|
|
action_param=( "${action_param[@]}" "--persistent" )
|
|
shift
|
|
;;
|
|
|
|
*) break
|
|
;;
|
|
esac
|
|
done
|
|
if [ $hasto -eq 0 ]
|
|
then
|
|
error "${action} requires a 'to' argument."
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
DNAT) action="DNAT"
|
|
if [ ! "${table}" = "nat" ]
|
|
then
|
|
error "${action} must on a the 'nat' table but table ${table} is given."
|
|
return 1
|
|
fi
|
|
local hasto=0
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1}" in
|
|
to|to-destination|--to-destination)
|
|
action_param=( "${action_param[@]}" "--to-destination" "${2}" )
|
|
# ports need a protocol: either tcp or udp (or both if unset)
|
|
if [[ "${2}" =~ ":" ]]
|
|
then
|
|
[ "${proto}" = "any" ] && proto="tcp udp"
|
|
require_protocol_with_action=("${proto[@]}")
|
|
fi
|
|
hasto=1
|
|
shift 2
|
|
;;
|
|
|
|
random|--random)
|
|
action_param=( "${action_param[@]}" "--random" )
|
|
shift
|
|
;;
|
|
|
|
persistent|--persistent)
|
|
action_param=( "${action_param[@]}" "--persistent" )
|
|
shift
|
|
;;
|
|
|
|
*) break
|
|
;;
|
|
esac
|
|
done
|
|
if [ $hasto -eq 0 ]
|
|
then
|
|
error "${action} requires a 'to' argument."
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
REDIRECT)
|
|
action="REDIRECT"
|
|
require_protocol_with_action=("${proto[@]}")
|
|
if [ ! "${table}" = "nat" ]
|
|
then
|
|
error "${action} must on a the 'nat' table but table ${table} is given."
|
|
return 1
|
|
fi
|
|
local hasto=0
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1}" in
|
|
to|to-port|--to-port|to-ports|--to-ports)
|
|
action_param=( "${action_param[@]}" "--to-ports" "${2}" )
|
|
# ports need a protocol: either tcp or udp (or both if unset)
|
|
test "${proto}" = "any" && proto="tcp udp"
|
|
require_protocol_with_action=("${proto[@]}")
|
|
hasto=1
|
|
shift 2
|
|
;;
|
|
|
|
random|--random)
|
|
action_param=( "${action_param[@]}" "--random" )
|
|
shift
|
|
;;
|
|
|
|
*) break
|
|
;;
|
|
esac
|
|
done
|
|
if [ $hasto -eq 0 ]
|
|
then
|
|
error "${action} requires a 'to' argument."
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
TPROXY) action="TPROXY"
|
|
action_param=()
|
|
if [ "${1}" = "mark" -o "${1}" = "tproxy-mark" -o "${1}" = "--tproxy-mark" ]
|
|
then
|
|
action_param=("--tproxy-mark" "${2}")
|
|
shift 2
|
|
fi
|
|
|
|
if [ "${1}" = "on-port" -o "${1}" = "to-port" -o "${1}" = "to" -o "${1}" = "--on-port" ]
|
|
then
|
|
action_param=("${action_param[@]}" "--on-port" "${2}")
|
|
shift 2
|
|
else
|
|
error "${action} requires a 'on-port' or 'on-ip' argument."
|
|
return 1
|
|
fi
|
|
|
|
if [ "${1}" = "on-ip" -o "${1}" = "--on-ip" -o "${1}" = "to-ip" ]
|
|
then
|
|
action_param=("${action_param[@]}" "--on-ip" "${2}")
|
|
shift 2
|
|
fi
|
|
|
|
if [ ! "${table}" = "mangle" ]
|
|
then
|
|
error "${action} cannot be on '$table', only on a the 'mangle' table."
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
TOS) action="TOS"
|
|
if [ "${1}" = "to" -o "${1}" = "--set-tos" ]
|
|
then
|
|
action_param=("--set-tos" "${2}")
|
|
shift 2
|
|
else
|
|
error "${action} requires a 'to' argument"
|
|
return 1
|
|
fi
|
|
if [ ! "${table}" = "mangle" ]
|
|
then
|
|
error "${action} cannot be on '$table', only on a the 'mangle' table."
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
MARK) action="MARK"
|
|
if [ "${1}" = "to" -o "${1}" = "--set-mark" ]
|
|
then
|
|
action_param=("--set-mark" "${2}")
|
|
shift 2
|
|
else
|
|
error "${action} requires a 'to' argument"
|
|
return 1
|
|
fi
|
|
if [ ! "${table}" = "mangle" ]
|
|
then
|
|
error "${action} cannot be on '$table', only on a the 'mangle' table."
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
CONNMARK)
|
|
action="CONNMARK"
|
|
case "${1}" in
|
|
to|--set-mark)
|
|
action_param=("--set-mark" "${2}")
|
|
shift 2
|
|
;;
|
|
save|--save-mark)
|
|
if [ "${2}" = "mask" -o "${2}" = "--mask" ]
|
|
then
|
|
action_param=("--save-mark" "--mask" "${3}")
|
|
shift 3
|
|
else
|
|
action_param=("--save-mark")
|
|
shift 1
|
|
fi
|
|
;;
|
|
restore|--restore-mark)
|
|
if [ "${2}" = "mask" -o "${2}" = "--mask" ]
|
|
then
|
|
action_param=("--restore-mark" "--mask" "${3}")
|
|
shift 3
|
|
else
|
|
action_param=("--restore-mark")
|
|
shift 1
|
|
fi
|
|
;;
|
|
*)
|
|
error "${action} requires a either 'to', 'save' or 'restore' argument"
|
|
return 1
|
|
;;
|
|
esac
|
|
if [ ! "${table}" = "mangle" ]
|
|
then
|
|
error "${action} cannot be on '$table', only on a the 'mangle' table."
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
DSCP) action="DSCP"
|
|
if [ "${1}" = "to" ]
|
|
then
|
|
if [ "${2}" = "class" ]
|
|
then
|
|
action_param=("--set-dscp-class" "${3}")
|
|
shift
|
|
else
|
|
action_param=("--set-dscp" "${2}")
|
|
fi
|
|
shift 2
|
|
elif [ "${1}" = "class" -o "${1}" = "--set-dscp-class" ]
|
|
then
|
|
action_param=("--set-dscp-class" "${2}")
|
|
shift
|
|
elif [ "${1}" = "--set-dscp" ]
|
|
then
|
|
action_param=("--set-dscp" "${2}")
|
|
shift
|
|
else
|
|
error "${action} requires a 'to' argument"
|
|
return 1
|
|
fi
|
|
if [ ! "${table}" = "mangle" ]
|
|
then
|
|
error "${action} cannot be on '$table', only on a the 'mangle' table."
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
SET) action="SET"
|
|
local hasadd=0 hasdel=0
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1}" in
|
|
add|set|add-set|--add-set)
|
|
action_param=( "${action_param[@]}" "--add-set" "${2}" "${3}" )
|
|
hasadd=1
|
|
shift 3
|
|
;;
|
|
|
|
del|unset|remove|del-set|--del-set)
|
|
action_param=( "${action_param[@]}" "--del-set" "${2}" "${3}" )
|
|
hasdel=1
|
|
shift 3
|
|
;;
|
|
|
|
exist|--exist)
|
|
action_param=( "${action_param[@]}" "--exist" )
|
|
shift
|
|
;;
|
|
|
|
timeout|--timeout)
|
|
action_param=( "${action_param[@]}" "--timeout" "${2}" )
|
|
shift 2
|
|
;;
|
|
|
|
*) break
|
|
;;
|
|
esac
|
|
done
|
|
if [ $hasadd -eq 0 -a $hasdel -eq 0 ]
|
|
then
|
|
error "${action} requires either 'add' or 'del' argument with at least name and type."
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
*)
|
|
chain_exists "${table}" "${action}"
|
|
local action_is_chain=$?
|
|
;;
|
|
esac
|
|
|
|
if [ "${1}" = "with" ]
|
|
then
|
|
if [ ${#action_param} -ne 0 ]
|
|
then
|
|
error "action ${action} does not accept 'with' parameters."
|
|
return 1
|
|
fi
|
|
|
|
shift
|
|
case "${1}" in
|
|
hashlimit)
|
|
action_param=("${1}" "${2}")
|
|
shift 2
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1}" in
|
|
upto|above|mode|burst|srcmask|dstmask|htable_size|htable-size|htable_max|htable-max|htable_expire|htable-expire|htable_gcinterval|htable-gcinterval)
|
|
action_param=("${action_param[@]}" "${1}" "${2}")
|
|
shift 2
|
|
continue
|
|
;;
|
|
*)
|
|
break
|
|
;;
|
|
esac
|
|
done
|
|
;;
|
|
|
|
connlimit)
|
|
action_param=("${1}")
|
|
shift
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1}" in
|
|
upto|above|mask)
|
|
action_param=("${action_param[@]}" "${1}" "${2}")
|
|
shift 2
|
|
continue
|
|
;;
|
|
|
|
saddr|daddr)
|
|
action_param=("${action_param[@]}" "${1}")
|
|
shift
|
|
continue
|
|
;;
|
|
|
|
*)
|
|
break
|
|
;;
|
|
esac
|
|
done
|
|
;;
|
|
|
|
limit|LIMIT)
|
|
action_param=("limit" "${2}" "${3}")
|
|
shift 3
|
|
|
|
if [ "${1}" = "overflow" ]
|
|
then
|
|
action_param[3]="overflow"
|
|
action_param[4]="${2}"
|
|
shift 2
|
|
fi
|
|
;;
|
|
|
|
recent|RECENT)
|
|
action_param=("recent" "${2}" "${3}" "${4}")
|
|
shift 4
|
|
;;
|
|
|
|
knock|KNOCK)
|
|
action_param=("knock" "${2}")
|
|
shift 2
|
|
;;
|
|
|
|
*)
|
|
error "Cannot understand action's '${action}' directive '${1}'"
|
|
return 1
|
|
;;
|
|
esac
|
|
fi
|
|
;;
|
|
|
|
*)
|
|
error "Cannot understand directive '${param}'."
|
|
return 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# ----------------------------------------------------------------------------------
|
|
# Validations
|
|
|
|
test -z "${table}" && table="filter"
|
|
[ ! -z "${FIREHOL_CHAIN_ALIASES[$table.$chain]}" ] && chain="${FIREHOL_CHAIN_ALIASES[$table.$chain]}"
|
|
|
|
if [ -z "${require_protocol_with_action[*]}" ]
|
|
then
|
|
error "Action ${action} requires a protocol to be given."
|
|
return 1
|
|
fi
|
|
|
|
# In FIREHOL_RULESET_MODE="optimal" (given as the parameter optimal=1) to us,
|
|
# we just create the state=NEW rules.
|
|
# We will work normally though if a helper is required.
|
|
if [ ${optimal} -eq 1 -a "${table}" = "filter" -a -z "${helper[*]}" -a "${proto[0]}" != "icmpv6" ]
|
|
then
|
|
if [[ "${state}" =~ 'NEW' ]]
|
|
then
|
|
# only NEW is required
|
|
# discard all other states
|
|
state="NEW"
|
|
else
|
|
# No NEW state found
|
|
# no need to generate these rules
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
|
|
# if there are custom arguments, keep the procol with it
|
|
[ ! -z "${custom}" -a "${require_protocol_with_action[*]}" = "any" ] && require_protocol_with_action=("${proto[@]}")
|
|
|
|
|
|
# If the user did not specify a rejection message,
|
|
# we have to be smart and produce a tcp-reset if the protocol
|
|
# is TCP and an ICMP port unreachable in all other cases.
|
|
# we will change the action to SMART_REJECT
|
|
|
|
if [ "${action}" = "REJECT" -a "${action_param[1]}" = "auto" ]
|
|
then
|
|
action="SMART_REJECT"
|
|
action_param=()
|
|
fi
|
|
|
|
if [ ${noowner} -eq 1 ]
|
|
then
|
|
uid=(any)
|
|
uidnot=
|
|
|
|
gid=(any)
|
|
gidnot=
|
|
fi
|
|
|
|
local physbridge="--physdev-is-bridged"
|
|
if [ ! "${work_cmd}" = "router" -a ! "${physin}${physout}" = "anyany" ]
|
|
then
|
|
if [ ! "${physin}" = "any" -a "${physout}" = "any" ]
|
|
then
|
|
physbridge="--physdev-is-in"
|
|
elif [ "${physin}" = "any" -a ! "${physout}" = "any" ]
|
|
then
|
|
physbridge="--physdev-is-out"
|
|
fi
|
|
fi
|
|
|
|
local srcnot= dstnot=
|
|
if running_both; then
|
|
if [ "${src4not}" != "${src6not}" ]
|
|
then
|
|
error "Mixed use of 'not' with src4 and src6." && return 1
|
|
else
|
|
srcnot="${src4not}"
|
|
fi
|
|
if [ "${dst4not}" != "${dst6not}" ]
|
|
then
|
|
error "Mixed use of 'not' with dst4 and dst6." && return 1
|
|
else
|
|
dstnot="${dst4not}"
|
|
fi
|
|
if [ "${src4[*]}" = "default" -a "${src6[*]}" != "default" ]
|
|
then
|
|
error "Must specify src4 when specifying src6" && return 1
|
|
fi
|
|
|
|
if [ "${dst4[*]}" = "default" -a "${dst6[*]}" != "default" ]
|
|
then
|
|
error "Must specify dst4 when specifying dst6" && return 1
|
|
fi
|
|
|
|
if [ "${src6[*]}" = "default" -a "${src4[*]}" != "default" ]
|
|
then
|
|
error "Must specify src6 when specifying src4" && return 1
|
|
fi
|
|
|
|
if [ "${dst6[*]}" = "default" -a "${dst4[*]}" != "default" ]
|
|
then
|
|
error "Must specify dst6 when specifying dst4" && return 1
|
|
fi
|
|
elif running_ipv6; then
|
|
srcnot="${src6not}"
|
|
dstnot="${dst6not}"
|
|
else
|
|
srcnot="${src4not}"
|
|
dstnot="${dst4not}"
|
|
fi
|
|
|
|
test "${src4[*]}" = "default" && src4=(any)
|
|
test "${dst4[*]}" = "default" && dst4=(any)
|
|
test "${src6[*]}" = "default" && src6=(any)
|
|
test "${dst6[*]}" = "default" && dst6=(any)
|
|
|
|
if [ ! "${src4[*]}" = "any" ]
|
|
then
|
|
src4=(${src4[*]//reserved_ips()/${RESERVED_IPV4}})
|
|
src4=(${src4[*]//private_ips()/${PRIVATE_IPV4}})
|
|
src4=(${src4[*]//multicast_ips()/${MULTICAST_IPV4}})
|
|
src4=(${src4[*]//unroutable_ips()/${UNROUTABLE_IPV4}})
|
|
fi
|
|
if [ ! "${dst4[*]}" = "any" ]
|
|
then
|
|
dst4=(${dst4[*]//reserved_ips()/${RESERVED_IPV4}})
|
|
dst4=(${dst4[*]//private_ips()/${PRIVATE_IPV4}})
|
|
dst4=(${dst4[*]//multicast_ips()/${MULTICAST_IPV4}})
|
|
dst4=(${dst4[*]//unroutable_ips()/${UNROUTABLE_IPV4}})
|
|
fi
|
|
|
|
if [ ! "${src6[*]}" = "any" ]
|
|
then
|
|
src6=(${src6[*]//reserved_ips()/${RESERVED_IPV6}})
|
|
src6=(${src6[*]//private_ips()/${PRIVATE_IPV6}})
|
|
src6=(${src6[*]//multicast_ips()/${MULTICAST_IPV6}})
|
|
src6=(${src6[*]//unroutable_ips()/${UNROUTABLE_IPV6}})
|
|
fi
|
|
if [ ! "${dst6[*]}" = "any" ]
|
|
then
|
|
dst6=(${dst6[*]//reserved_ips()/${RESERVED_IPV6}})
|
|
dst6=(${dst6[*]//private_ips()/${PRIVATE_IPV6}})
|
|
dst6=(${dst6[*]//multicast_ips()/${MULTICAST_IPV6}})
|
|
dst6=(${dst6[*]//unroutable_ips()/${UNROUTABLE_IPV6}})
|
|
fi
|
|
|
|
[ ! -z "${push_flow_inheritance_type}" ] && push_flow_inheritance "${push_flow_inheritance_type}" "${action}" "${infacenot//!/not}" "${inface[*]}" "${outfacenot//!/not}" "${outface[*]}" "${srcnot//!/not}" "${src4[*]}" "${src6[*]}" "${dstnot//!/not}" "${dst4[*]}" "${dst6[*]}"
|
|
|
|
# ----------------------------------------------------------------------------------
|
|
# Preparations for the main loop
|
|
|
|
local -a \
|
|
addrtype_arg=() stp_arg=() dtp_arg=() state_arg=() \
|
|
logopts_arg=() \
|
|
uid_arg=() owner_arg=() gid_arg=() \
|
|
mark_arg=() tos_arg=() dscp_arg=() proto_arg=() \
|
|
inf_arg=() outf_arg=() inph_arg=() physdev_arg=() outph_arg=() \
|
|
mc_arg=() s_arg=() d_arg=() sp_arg=() dp_arg=() \
|
|
basecmd=() protected_args=() positive_args=()
|
|
|
|
local logrule= ipvall= \
|
|
tuid= tgid= \
|
|
tmark= ttos= tdscp= pr= \
|
|
inf= outf= inph= outph= \
|
|
mc= ipv= iptables= src= dst= s= d= sp= dp= \
|
|
not= src_has_ipset=0 dst_has_ipset=0 \
|
|
DYNAMIC_CHAIN_COUNTER attachment=
|
|
|
|
# log / loglimit
|
|
case "${log}" in
|
|
'')
|
|
logrule=none
|
|
;;
|
|
|
|
limit)
|
|
logrule=limit
|
|
prepare_iptables_log_arg "${logtxt}" || return 1
|
|
logopts_arg=("${FIREHOL_LOG_IPTABLES_ARG[@]}")
|
|
;;
|
|
|
|
normal)
|
|
logrule=normal
|
|
prepare_iptables_log_arg "${logtxt}" || return 1
|
|
logopts_arg=("${FIREHOL_LOG_IPTABLES_ARG[@]}")
|
|
;;
|
|
|
|
*)
|
|
error "Unknown log value '${log}'."
|
|
;;
|
|
esac
|
|
|
|
# keep a list of all ip versions we need
|
|
running_ipv4 && ipvall="ipv4"
|
|
running_ipv6 && ipvall="${ipvall} ipv6"
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
# BRANCH OPTIMIZATION
|
|
#
|
|
# Facts: - the caller expects us to add statements to a chain and
|
|
# perform the action only if all conditions are met - AND'd
|
|
# (multiple options to the same condition which are OR'd).
|
|
#
|
|
# example:
|
|
#
|
|
# inface "eth0 eth1" outface eth2 =
|
|
# ( ( inface=eth0 OR inface=eth1 ) AND outface=eth2 )
|
|
#
|
|
# while
|
|
#
|
|
# inface not "eth0 eth1" outface eth2 =
|
|
# ( inface!=eth0 AND inface!=eth1 AND outface=eth2 )
|
|
#
|
|
# All conditions that are negative and have only one possible
|
|
# value to match, can be executed in the main loop (negative
|
|
# and positive conditions on the same command).
|
|
#
|
|
# - we can only branch (i.e. create a chain and jump to it) only
|
|
# if the action is not RETURN. If it is RETURN, then we have
|
|
# only one exit point and we cannot do any complex expressions
|
|
#
|
|
# - A branch is required when we have multiple values in
|
|
# positive conditions, to avoid executing certain features
|
|
# multiple times (I call these features: protected
|
|
# parameters, because we should protect them from multiple
|
|
# executions - a branch is cheaper than 2 of these)
|
|
#
|
|
# These require branching always:
|
|
# (we can only avoid it if we have a negative branch)
|
|
#
|
|
# * log
|
|
# * loglimit
|
|
# * accounting
|
|
#
|
|
# These require branching if we are going to generate multiple statements:
|
|
# (so that they are applied only once per rule)
|
|
#
|
|
# * limit
|
|
# * connlimit
|
|
# * hashlimit
|
|
# * ipset
|
|
# * helpers
|
|
# * custom rules
|
|
#
|
|
# We will not stop though if we are not allowed to branch for them.
|
|
# We will just issue a warning that the generated firewall is not optimal.
|
|
#
|
|
# Let's calculate a few sums to help us take decisions:
|
|
|
|
local positive_single=0 negative_single=0 positive_multi=0 negative_multi=0 action_is_our_branch=0 placed_protected_in_a_branch=0 have_protected=0
|
|
#
|
|
# positive_single - counts the number of positive parameters that have just one possible value
|
|
# negative_single - counts the number of negative parameters that have just one possible value
|
|
# positive_multi - counts the number of positive parameters that have multiple values
|
|
# negative_multi - counts the number of negative parameters that have multiple values
|
|
# action_is_our_branch - is set when we create our branch
|
|
# placed_protected_in_a_branch - is set if we place the log, loglimit, accounting, limit, connlimit, ipset in our branch
|
|
# have_protected - is set when there are protected parameters
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
# prepare the protected parameters
|
|
# protected = we will try run them only once.
|
|
# We should put here matches that are more expensive than branching.
|
|
|
|
protected_args=(${custom})
|
|
|
|
# limit
|
|
[ ! -z "${limit}" ] && protected_args=("${protected_args[@]}" "-m" "limit" \
|
|
"--limit" "${limit}" \
|
|
"--limit-burst" "${burst}")
|
|
|
|
# connlimit
|
|
if [ ! -z "${connlimit}" ]
|
|
then
|
|
protected_args=("${protected_args[@]}" "-m" "connlimit" \
|
|
"--connlimit-${connlimit_type}" "${connlimit}" \
|
|
"--connlimit-${connlimit_addr}")
|
|
|
|
[ ! -z "${connlimit_mask}" ] && protected_args=("${protected_args[@]}" "--connlimit-mask" "${connlimit_mask}")
|
|
fi
|
|
|
|
# hashlimit
|
|
if [ ! -z "${hashlimit}" ]
|
|
then
|
|
protected_args=("${protected_args[@]}" "-m" "hashlimit" \
|
|
"--hashlimit-name" "${hashlimit_name}" \
|
|
"--hashlimit-${hashlimit_type}" "${hashlimit}" \
|
|
"--hashlimit-mode" "${hashlimit_mode}")
|
|
|
|
[ ! -z "${hashlimit_burst}" ] && protected_args=("${protected_args[@]}" "--hashlimit-burst" "${hashlimit_burst}")
|
|
[ ! -z "${hashlimit_srcmask}" ] && protected_args=("${protected_args[@]}" "--hashlimit-srcmask" "${hashlimit_srcmask}")
|
|
[ ! -z "${hashlimit_dstmask}" ] && protected_args=("${protected_args[@]}" "--hashlimit-dstmask" "${hashlimit_dstmask}")
|
|
[ ! -z "${hashlimit_htable_size}" ] && protected_args=("${protected_args[@]}" "--hashlimit-htable-size" "${hashlimit_htable_size}")
|
|
[ ! -z "${hashlimit_htable_max}" ] && protected_args=("${protected_args[@]}" "--hashlimit-htable-max" "${hashlimit_htable_max}")
|
|
[ ! -z "${hashlimit_htable_expire}" ] && protected_args=("${protected_args[@]}" "--hashlimit-htable-expire" "${hashlimit_htable_expire}")
|
|
[ ! -z "${hashlimit_htable_gcinterval}" ] && protected_args=("${protected_args[@]}" "--hashlimit-htable-gcinterval" "${hashlimit_htable_gcinterval}")
|
|
fi
|
|
|
|
# ipset
|
|
[ ! -z "${ipsetname}" ] && protected_args=("${protected_args[@]}" "-m" "set" ${ipsetnot} "--match-set" "${ipsetname}" "${ipsetflags}" ${ipsetopts})
|
|
|
|
# helpers
|
|
for x in ${helper[@]}
|
|
do
|
|
protected_args=("${protected_args[@]}" "-m" "helper" ${helpernot} "--helper" "${x}")
|
|
done
|
|
|
|
if [ ${FIREHOL_PROTECTED_MATCHES} -eq 1 ]
|
|
then
|
|
[ ! -z "${protected_args[*]}" ] && have_protected=1
|
|
else
|
|
positive_args=("${protected_args[@]}")
|
|
protected_args=()
|
|
have_protected=0
|
|
fi
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
# prepare the positive parameters
|
|
# these are parameters that will be executed, any number of times,
|
|
# together with the positive statements.
|
|
|
|
if [ ${FIREHOL_SUPPORT_MULTIPORT} -ne 0 ]
|
|
then
|
|
# we cannot have both source ports and destination ports as multiport
|
|
# we have to pick
|
|
|
|
sp=0
|
|
dp=0
|
|
|
|
# pick the one with the most ports
|
|
if [ "${#sport[*]}" -ge "${#dport[*]}" ]
|
|
then
|
|
sp=1
|
|
else
|
|
dp=1
|
|
fi
|
|
|
|
if [ ${sp} -eq 1 -a "${#sport[*]}" -gt 1 ]
|
|
then
|
|
# do sport
|
|
sp_arg=()
|
|
x=0
|
|
src=
|
|
|
|
for s in ${sport[@]}
|
|
do
|
|
# echo >&2 "Adding ${s}, x=${x}, src=${src}"
|
|
if [ ${x} -ge 14 ]
|
|
then
|
|
sp_arg=("${sp_arg[@]}" "multiport:${src}")
|
|
src=
|
|
x=0
|
|
fi
|
|
test ! -z "${src}" && src="${src},"
|
|
case "${s}" in
|
|
any|ANY) continue ;;
|
|
*-*|*:*) (( x += 2 )); src="${src}${s//-/:}" ;;
|
|
*) (( x += 1 )); src="${src}${s}" ;;
|
|
esac
|
|
done
|
|
|
|
if [ ! -z "${src}" ]
|
|
then
|
|
if [ ${x} -gt 1 ]
|
|
then
|
|
sp_arg=("${sp_arg[@]}" "multiport:${src}")
|
|
else
|
|
sp_arg=("${sp_arg[@]}" "${src}")
|
|
fi
|
|
fi
|
|
|
|
sport=("${sp_arg[@]}")
|
|
sp_arg=()
|
|
src=
|
|
x=
|
|
fi
|
|
sp=
|
|
|
|
if [ ${dp} -eq 1 -a "${#dport[*]}" -gt 1 ]
|
|
then
|
|
# do dport
|
|
dp_arg=()
|
|
x=0
|
|
dst=
|
|
|
|
for d in ${dport[@]}
|
|
do
|
|
# echo >&2 "Adding ${d}, x=${x}, dst=${dst}"
|
|
if [ ${x} -ge 14 ]
|
|
then
|
|
dp_arg=("${dp_arg[@]}" "multiport:${dst}")
|
|
dst=
|
|
x=0
|
|
fi
|
|
test ! -z "${dst}" && dst="${dst},"
|
|
case "${d}" in
|
|
any|ANY) continue ;;
|
|
*-*|*:*) (( x += 2 )); dst="${dst}${d//-/:}" ;;
|
|
*) (( x += 1 )); dst="${dst}${d}" ;;
|
|
esac
|
|
done
|
|
|
|
if [ ! -z "${dst}" ]
|
|
then
|
|
if [ ${x} -gt 1 ]
|
|
then
|
|
dp_arg=("${dp_arg[@]}" "multiport:${dst}")
|
|
else
|
|
dp_arg=("${dp_arg[@]}" "${dst}")
|
|
fi
|
|
fi
|
|
|
|
dport=("${dp_arg[@]}")
|
|
dp_arg=()
|
|
dst=
|
|
x=
|
|
fi
|
|
dp=
|
|
fi
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
# make the calculations
|
|
|
|
if [ "${#inface[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${infacenot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${infacenot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
if [ "${#outface[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${outfacenot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${outfacenot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
if [ "${#physin[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${physinnot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${physinnot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
if [ "${#physout[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${physoutnot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${physoutnot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
if [ "${#mac[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${macnot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${macnot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
if [ "${#src4[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${srcnot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${srcnot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
[[ "${src4[*]}" =~ 'ipset:' ]] && src_has_ipset=1
|
|
if [ "${#dst4[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${dstnot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${dstnot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
[[ "${dst4[*]}" =~ 'ipset:' ]] && dst_has_ipset=1
|
|
if [ "${#src6[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${srcnot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${srcnot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
[[ "${src6[*]}" =~ 'ipset:' ]] && src_has_ipset=1
|
|
if [ "${#dst6[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${dstnot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${dstnot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
[[ "${dst6[*]}" =~ 'ipset:' ]] && dst_has_ipset=1
|
|
if [ "${#proto[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${protonot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${protonot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
if [ "${#sport[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${sportnot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${sportnot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
if [ "${#dport[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${dportnot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${dportnot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
if [ "${#mark[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${marknot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${marknot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
if [ "${#tos[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${tosnot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${tosnot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
if [ "${#dscp[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${dscpnot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${dscpnot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
if [ "${#uid[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${uidnot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${uidnot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
if [ "${#gid[*]}" -gt 1 ]
|
|
then
|
|
if [ -z "${gidnot}" ]; then (( positive_multi += 1 )) ; else (( negative_multi += 1)) ; fi
|
|
else
|
|
if [ -z "${gidnot}" ]; then (( positive_single += 1 )); else (( negative_single += 1)); fi
|
|
fi
|
|
# ignore 'state', 'srctype', 'dsttype' are not counted, since they are negated in the positive rules
|
|
|
|
# Test cases:
|
|
#
|
|
# multiple negatives with logging
|
|
# mark4 10 OUTPUT src not "1.1.1.1 2.2.2.2" log "matched all packets except from 1.1.1.1 and 2.2.2.2"
|
|
#
|
|
# branch to log
|
|
# mark4 10 OUTPUT src "1.1.1.1 2.2.2.2" dst "3.3.3.3 4.4.4.4" log "branch to log"
|
|
#
|
|
# double branching for protected matches and logging
|
|
# mark4 10 OUTPUT src "1.1.1.1 2.2.2.2" dst "3.3.3.3 4.4.4.4" connlimit 10 32 log "double branch for connlimit and log"
|
|
#
|
|
#
|
|
|
|
# ---------------------------------------------------------------------
|
|
# process the negative rules
|
|
|
|
if [ ${negative_multi} -gt 0 ]
|
|
then
|
|
local negative_chain= negative_action=
|
|
|
|
if [ "${action}" = "RETURN" ]
|
|
then
|
|
error "iptables cannot do this! You have requested multiple values in a NOT rule. This is OK, except when the action is RETURN. You hit that case. Sorry..."
|
|
return 1
|
|
fi
|
|
|
|
if [ ${return_if_not_matched} -eq 1 ]
|
|
then
|
|
# we can return from this chain
|
|
# add the negatives here
|
|
|
|
negative_chain="${chain}"
|
|
negative_action=
|
|
not="!"
|
|
|
|
# echo >&2 " >>> ${FUNCNAME}: NEGATIVES: RETURNING IF NOT MATCHED"
|
|
elif [ ${action_is_chain} -eq 1 ]
|
|
then
|
|
# if the action is a chain name, then just add the negative
|
|
# expressions to this chain. Nothing more.
|
|
|
|
negative_chain="${action}"
|
|
negative_action=
|
|
not=
|
|
|
|
# echo >&2 " >>> ${FUNCNAME}: NEGATIVES: TARGET ACTION IS CHAIN"
|
|
else
|
|
# if the action is a native iptables action, then create
|
|
# an intermediate chain to store the negative expression,
|
|
# and change the action of the rule to point to this action.
|
|
|
|
# In this case, below we add after all negatives, the original
|
|
# action of the rule.
|
|
get_next_dynamic_counter DYNAMIC_CHAIN_COUNTER
|
|
negative_chain="${chain}.${DYNAMIC_CHAIN_COUNTER}"
|
|
|
|
iptables_both -t ${table} -N "${negative_chain}"
|
|
negative_action="${action}"
|
|
action="${negative_chain}"
|
|
not=
|
|
|
|
# notify the positive rules that the action is our branch
|
|
action_is_our_branch=1
|
|
# echo >&2 " >>> ${FUNCNAME}: NEGATIVES: BRANCHING TO NEW CHAIN ${negative_action}"
|
|
fi
|
|
|
|
if [ ! -z "${infacenot}" -a "${#inface[*]}" -gt 1 ]
|
|
then
|
|
for inf in ${inface[*]}
|
|
do
|
|
iptables_both -t ${table} -A "${negative_chain}" ${not} -i "${inf}" -j RETURN
|
|
done
|
|
infacenot=
|
|
inface=(any)
|
|
fi
|
|
|
|
if [ ! -z "${outfacenot}" -a "${#outface[*]}" -gt 1 ]
|
|
then
|
|
for outf in ${outface[*]}
|
|
do
|
|
iptables_both -t ${table} -A "${negative_chain}" ${not} -o "${outf}" -j RETURN
|
|
done
|
|
outfacenot=
|
|
outface=(any)
|
|
fi
|
|
|
|
if [ ! -z "${physinnot}" -a "${#physin[*]}" -gt 1 ]
|
|
then
|
|
for inph in ${physin[*]}
|
|
do
|
|
iptables_both -t ${table} -A "${negative_chain}" -m physdev ${physbridge} ${not} --physdev-in "${inph}" -j RETURN
|
|
done
|
|
physinnot=
|
|
physin=(any)
|
|
fi
|
|
|
|
if [ ! -z "${physoutnot}" -a "${#physout[*]}" -gt 1 ]
|
|
then
|
|
for outph in ${physout[*]}
|
|
do
|
|
iptables_both -t ${table} -A "${negative_chain}" -m physdev ${physbridge} ${not} --physdev-out "${outph}" -j RETURN
|
|
done
|
|
physoutnot=
|
|
physout=(any)
|
|
fi
|
|
|
|
if [ ! -z "${macnot}" -a "${#mac[*]}" -gt 1 ]
|
|
then
|
|
for mc in ${mac[*]}
|
|
do
|
|
iptables_both -t ${table} -A "${negative_chain}" -m mac ${not} --mac-source "${mc}" -j RETURN
|
|
done
|
|
macnot=
|
|
mac=(any)
|
|
fi
|
|
|
|
if [ ! -z "${srcnot}" -a \( ${src_has_ipset} -eq 1 -o "${#src4[*]}" -gt 1 -o "${#src6[*]}" -gt 1 \) ]
|
|
then
|
|
for ipv in ${ipvall}
|
|
do
|
|
case "${ipv}" in
|
|
ipv4) iptables="iptables"
|
|
src="${src4[*]}"
|
|
;;
|
|
|
|
ipv6) iptables="ip6tables"
|
|
src="${src6[*]}"
|
|
;;
|
|
esac
|
|
|
|
for s in ${src}
|
|
do
|
|
case "${s}" in
|
|
ipset:*)
|
|
[ ${IPSET_WARNING} -eq 1 ] && ipset_warning
|
|
[ ${IPRANGE_WARNING} -eq 1 ] && iprange_warning
|
|
s="${s/ipset:/}"
|
|
test -z "${FIREHOL_IPSETS_USED[$s]}" && FIREHOL_IPSETS_USED[$s]="USED"
|
|
${iptables} -t ${table} -A "${negative_chain}" -m set ${not} --match-set "${s}" src ${IPSET_SRC_DST_OPTIONS} -j RETURN
|
|
;;
|
|
*:*-*|[0-9]*-*)
|
|
${iptables} -t ${table} -A "${negative_chain}" -m iprange ${not} --src-range "${s}" -j RETURN
|
|
;;
|
|
*)
|
|
${iptables} -t ${table} -A "${negative_chain}" ${not} -s "${s}" -j RETURN
|
|
;;
|
|
esac
|
|
done
|
|
done
|
|
srcnot=
|
|
src4=(any)
|
|
src6=(any)
|
|
fi
|
|
|
|
if [ ! -z "${dstnot}" -a \( ${dst_has_ipset} -eq 1 -o "${#dst4[*]}" -gt 1 -o "${#dst6[*]}" -gt 1 \) ]
|
|
then
|
|
for ipv in ${ipvall}
|
|
do
|
|
case "${ipv}" in
|
|
ipv4) iptables="iptables"
|
|
dst="${dst4[*]}"
|
|
;;
|
|
|
|
ipv6) iptables="ip6tables"
|
|
dst="${dst6[*]}"
|
|
;;
|
|
esac
|
|
|
|
for d in ${dst}
|
|
do
|
|
case "${d}" in
|
|
ipset:*)
|
|
[ ${IPSET_WARNING} -eq 1 ] && ipset_warning
|
|
[ ${IPRANGE_WARNING} -eq 1 ] && iprange_warning
|
|
d="${d/ipset:/}"
|
|
test -z "${FIREHOL_IPSETS_USED[$d]}" && FIREHOL_IPSETS_USED[$d]="USED"
|
|
${iptables} -t ${table} -A "${negative_chain}" -m set ${not} --match-set "${d}" dst ${IPSET_SRC_DST_OPTIONS} -j RETURN
|
|
;;
|
|
*:*-*|[0-9]*-*)
|
|
${iptables} -t ${table} -A "${negative_chain}" -m iprange ${not} --dst-range "${d}" -j RETURN
|
|
;;
|
|
*)
|
|
${iptables} -t ${table} -A "${negative_chain}" ${not} -d "${d}" -j RETURN
|
|
;;
|
|
esac
|
|
done
|
|
done
|
|
dstnot=
|
|
dst4=(any)
|
|
dst6=(any)
|
|
fi
|
|
|
|
if [ ! -z "${protonot}" -a "${#proto[*]}" -gt 1 ]
|
|
then
|
|
if [ ! -z "${sportnot}" -o ! -z "${dportnot}" ]
|
|
then
|
|
error "Cannot have negative protocol(s) and positive source/destination port(s)."
|
|
return 1
|
|
fi
|
|
|
|
for pr in ${proto[*]}
|
|
do
|
|
iptables_both -t ${table} -A "${negative_chain}" ${not} -p "${pr}" -j RETURN
|
|
done
|
|
protonot=
|
|
proto=(any)
|
|
fi
|
|
|
|
if [ ! -z "${sportnot}" -a "${#sport[*]}" -gt 1 ]
|
|
then
|
|
if [ "${proto}" = "any" ]
|
|
then
|
|
error "Cannot have negative source port without a protocol."
|
|
return 1
|
|
fi
|
|
|
|
for pr in ${proto[*]}
|
|
do
|
|
for sp in ${sport[*]}
|
|
do
|
|
case "${sp}" in
|
|
multiport:*)
|
|
sp="${sp/multiport:/}"
|
|
iptables_both -t ${table} -A "${negative_chain}" -p "${pr}" -m multiport ${not} --source-ports "${sp//-/:}" -j RETURN
|
|
;;
|
|
|
|
*)
|
|
iptables_both -t ${table} -A "${negative_chain}" -p "${pr}" ${not} --sport "${sp//-/:}" -j RETURN
|
|
;;
|
|
esac
|
|
done
|
|
done
|
|
sportnot=
|
|
sport=(any)
|
|
fi
|
|
|
|
if [ ! -z "${dportnot}" -a "${#dport[*]}" -gt 1 ]
|
|
then
|
|
if [ "${proto}" = "any" ]
|
|
then
|
|
error "Cannot have negative destination port without a protocol."
|
|
return 1
|
|
fi
|
|
|
|
for pr in ${proto[*]}
|
|
do
|
|
for dp in ${dport[*]}
|
|
do
|
|
case "${sp}" in
|
|
multiport:*)
|
|
dp="${dp/multiport:/}"
|
|
iptables_both -t ${table} -A "${negative_chain}" -p "${pr}" -m multiport ${not} --destination-ports "${dp//-/:}" -j RETURN
|
|
;;
|
|
|
|
*)
|
|
iptables_both -t ${table} -A "${negative_chain}" -p "${pr}" ${not} --dport "${dp//-/:}" -j RETURN
|
|
;;
|
|
esac
|
|
done
|
|
done
|
|
dportnot=
|
|
dport=(any)
|
|
fi
|
|
|
|
if [ ! -z "${uidnot}" -a "${#uid[*]}" -gt 1 ]
|
|
then
|
|
for tuid in ${uid[*]}
|
|
do
|
|
iptables_both -t ${table} -A "${negative_chain}" -m owner ${not} --uid-owner "${tuid}" -j RETURN
|
|
done
|
|
uidnot=
|
|
uid=(any)
|
|
fi
|
|
|
|
if [ ! -z "${gidnot}" -a "${#gid[*]}" -gt 1 ]
|
|
then
|
|
for tgid in ${gid[*]}
|
|
do
|
|
iptables_both -t ${table} -A "${negative_chain}" -m owner ${not} --gid-owner "${tgid}" -j RETURN
|
|
done
|
|
gidnot=
|
|
gid=(any)
|
|
fi
|
|
|
|
if [ ! -z "${marknot}" -a "${#mark[*]}" -gt 1 ]
|
|
then
|
|
for tmark in ${mark[*]}
|
|
do
|
|
iptables_both -t ${table} -A "${negative_chain}" -m mark ${not} --mark "${tmark}" -j RETURN
|
|
done
|
|
marknot=
|
|
mark=(any)
|
|
fi
|
|
|
|
if [ ! -z "${tosnot}" -a "${#tos[*]}" -gt 1 ]
|
|
then
|
|
for ttos in ${tos[*]}
|
|
do
|
|
iptables_both -t ${table} -A "${negative_chain}" -m tos ${not} --tos "${ttos}" -j RETURN
|
|
done
|
|
tosnot=
|
|
tos=(any)
|
|
fi
|
|
|
|
if [ ! -z "${dscpnot}" -a "${#dscp[*]}" -gt 1 ]
|
|
then
|
|
for tdscp in ${dscp[*]}
|
|
do
|
|
iptables_both -t ${table} -A "${negative_chain}" -m dscp ${not} --dscp${dscptype} "${tdscp}" -j RETURN
|
|
done
|
|
dscpnot=
|
|
dscp=(any)
|
|
fi
|
|
|
|
if [ ! -z "${negative_action}" ]
|
|
then
|
|
# in case this is temporary chain we created for the negative expression,
|
|
# just make it have the final action of the rule.
|
|
|
|
if [ "${negative_action}" = "${negative_chain}" ]
|
|
then
|
|
error "Cannot create an infinite loop chain."
|
|
return 1
|
|
fi
|
|
|
|
for ipv in ${ipvall}
|
|
do
|
|
case "${ipv}" in
|
|
ipv4) iptables="iptables";;
|
|
ipv6) iptables="ip6tables";;
|
|
esac
|
|
|
|
# since this is the target of the positive rules,
|
|
# logging and accounting can be attached here
|
|
if [ "$logrule" = "limit" ]; then ${iptables} -t ${table} -A "${negative_chain}" -m limit --limit "${FIREHOL_LOG_FREQUENCY}" --limit-burst "${FIREHOL_LOG_BURST}" -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}" || failed=$[failed + 1]; fi
|
|
if [ "$logrule" = "normal" ]; then ${iptables} -t ${table} -A "${negative_chain}" -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}" || failed=$[failed + 1]; fi
|
|
if [ ! -z "${accounting}" ]; then ${iptables} -t ${table} -A "${negative_chain}" -m nfacct --nfacct-name "${accounting}" || failed=$[failed + 1]; fi
|
|
|
|
for pr in ${require_protocol_with_action[*]}
|
|
do
|
|
[ "${pr}" = "any" ] && pr=
|
|
[ ! -z "${pr}" ] && pr="-p ${pr}"
|
|
|
|
# the original action of the rule
|
|
# we cannot add positive_args or protected_args here - if we do, the logging and accounting will be broken
|
|
rule_action_param ${iptables} "${negative_action}" "" "" "${table}" "${connlog}" "${action_param[@]}" -- -t ${table} -A "${negative_chain}" ${pr} "${protected_args[@]}" || failed=$[failed + 1]
|
|
done
|
|
done
|
|
|
|
# The positive rules will just send traffic to a
|
|
# chains - there is not need for params
|
|
action_param=()
|
|
require_protocol_with_action=(any)
|
|
protected_args=()
|
|
have_protected=0
|
|
|
|
# disable logging and accounting
|
|
# already did it above
|
|
logrule=none
|
|
accounting=
|
|
fi
|
|
fi
|
|
|
|
|
|
# ----------------------------------------------------------------------------------
|
|
# If we have not placed logging and accounting in the negative branch we may have to
|
|
# branch to put them here, only if the action is not RETURN.
|
|
#
|
|
# If it is RETURN, it will be executed with the positive rules.
|
|
|
|
if [ \( ${positive_multi} -gt 0 -o ! -z "${positive_rule_number}" \) -a \( ! "${logrule}" = "none" -o ! -z "${accounting}" \) -a ! "${action}" = "RETURN" ]
|
|
then
|
|
local logging_chain= logging_action=
|
|
|
|
# echo >&2 " >>> ${FUNCNAME}: MOVING LOGGING AND ACCOUNTING TO BRANCH"
|
|
|
|
get_next_dynamic_counter DYNAMIC_CHAIN_COUNTER
|
|
logging_chain="${chain}.${DYNAMIC_CHAIN_COUNTER}"
|
|
|
|
iptables_both -t ${table} -N "${logging_chain}"
|
|
logging_action="${action}"
|
|
action="${logging_chain}"
|
|
|
|
# notify the positive rules that the action is our branch
|
|
action_is_our_branch=1
|
|
|
|
if [ "${logging_action}" = "${logging_chain}" ]
|
|
then
|
|
error "Cannot create an infinite loop chain."
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "${logging_action}" ]
|
|
then
|
|
error "Cannot create iptables commands without an action."
|
|
return 1
|
|
fi
|
|
|
|
for ipv in ${ipvall}
|
|
do
|
|
case "${ipv}" in
|
|
ipv4) iptables="iptables";;
|
|
ipv6) iptables="ip6tables";;
|
|
esac
|
|
|
|
if [ "$logrule" = "limit" ]; then ${iptables} -t ${table} -A "${logging_chain}" -m limit --limit "${FIREHOL_LOG_FREQUENCY}" --limit-burst "${FIREHOL_LOG_BURST}" -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}" || failed=$[failed + 1]; fi
|
|
if [ "$logrule" = "normal" ]; then ${iptables} -t ${table} -A "${logging_chain}" -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}" || failed=$[failed + 1]; fi
|
|
if [ ! -z "${accounting}" ]; then ${iptables} -t ${table} -A "${logging_chain}" -m nfacct --nfacct-name "${accounting}" || failed=$[failed + 1]; fi
|
|
|
|
for pr in ${require_protocol_with_action[*]}
|
|
do
|
|
[ "${pr}" = "any" ] && pr=
|
|
[ ! -z "${pr}" ] && pr="-p ${pr}"
|
|
|
|
# the original action of the rule
|
|
# we cannot add positive_args or protected_args here - if we do, the logging and accounting will be broken
|
|
rule_action_param ${iptables} "${logging_action}" "" "" "${table}" "${connlog}" "${action_param[@]}" -- -t ${table} -A "${logging_chain}" ${pr} "${protected_args[@]}" || failed=$[failed + 1]
|
|
done
|
|
done
|
|
|
|
# The positive rules will just send traffic to a
|
|
# chains - there is not need for params
|
|
action_param=()
|
|
require_protocol_with_action=(any)
|
|
protected_args=()
|
|
have_protected=0
|
|
|
|
# disable logging and accounting
|
|
# already did it above
|
|
logrule=none
|
|
accounting=
|
|
fi
|
|
|
|
|
|
# ----------------------------------------------------------------------------------
|
|
# If there are protected parameters and we will generate multiple statements
|
|
# we have to branch
|
|
|
|
if [ ${have_protected} -eq 1 -a ${positive_multi} -gt 0 ]
|
|
then
|
|
local protected_chain= protected_action=
|
|
|
|
if [ "${action}" = "RETURN" ]
|
|
then
|
|
warning "Generated iptables rules may not be optimal."
|
|
else
|
|
# echo >&2 " >>> ${FUNCNAME}: MOVING PROTECTED PARAMS TO BRANCH"
|
|
|
|
get_next_dynamic_counter DYNAMIC_CHAIN_COUNTER
|
|
protected_chain="${chain}.${DYNAMIC_CHAIN_COUNTER}"
|
|
|
|
iptables_both -t ${table} -N "${protected_chain}"
|
|
protected_action="${action}"
|
|
action="${protected_chain}"
|
|
|
|
# notify the positive rules that the action is our branch
|
|
action_is_our_branch=1
|
|
|
|
if [ "${protected_action}" = "${protected_chain}" ]
|
|
then
|
|
error "Cannot create an infinite loop chain."
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "${protected_action}" ]
|
|
then
|
|
error "Cannot create iptables commands without an action."
|
|
return 1
|
|
fi
|
|
|
|
for ipv in ${ipvall}
|
|
do
|
|
case "${ipv}" in
|
|
ipv4) iptables="iptables";;
|
|
ipv6) iptables="ip6tables";;
|
|
esac
|
|
|
|
for pr in ${require_protocol_with_action[*]}
|
|
do
|
|
[ "${pr}" = "any" ] && pr=
|
|
[ ! -z "${pr}" ] && pr="-p ${pr}"
|
|
|
|
# the original action of the rule
|
|
rule_action_param ${iptables} "${protected_action}" "" "" "${table}" "${connlog}" "${action_param[@]}" -- -t ${table} -A "${protected_chain}" ${pr} "${protected_args[@]}" || failed=$[failed + 1]
|
|
done
|
|
done
|
|
|
|
# notify the positive rules that we have placed all protected in the branch
|
|
placed_protected_in_a_branch=1
|
|
|
|
# empty the constrained parameters
|
|
# so that the positive rules do not add them again
|
|
protected_args=()
|
|
have_protected=0
|
|
|
|
# The positive rules will just send traffic to a
|
|
# chains - there is not need for params
|
|
action_param=()
|
|
require_protocol_with_action=(any)
|
|
fi
|
|
fi
|
|
|
|
# ----------------------------------------------------------------------------------
|
|
# Process the positive rules
|
|
|
|
FIREHOL_RULE_POSITIVE_STATEMENTS_GENERATED=0
|
|
|
|
if [ "${action}" = "${chain}" ]
|
|
then
|
|
error "Cannot create an infinite loop chain."
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "${action}" ]
|
|
then
|
|
error "Cannot create iptables commands without an action."
|
|
return 1
|
|
fi
|
|
|
|
if [ -z "${chain}" ]
|
|
then
|
|
error "Cannot create iptables commands without a chain to attach them to."
|
|
return 1
|
|
fi
|
|
|
|
# addrtype (srctype, dsttype)
|
|
# this accepts a comma separated list of type, so no need to loop
|
|
if [ ! -z "${srctype}${dsttype}" ]
|
|
then
|
|
addrtype_arg=("-m" "addrtype")
|
|
|
|
if [ ! -z "${srctype}" ]
|
|
then
|
|
stp_arg=(${srctypenot} "--src-type" "${srctype}")
|
|
fi
|
|
|
|
if [ ! -z "${dsttype}" ]
|
|
then
|
|
dtp_arg=(${dsttypenot} "--dst-type" "${dsttype}")
|
|
fi
|
|
fi
|
|
|
|
# state
|
|
[ ! -z "${state}" ] && state_arg=("-m" "conntrack" ${statenot} "--ctstate" "${state}")
|
|
|
|
if [ -z "${positive_rule_number}" ]
|
|
then
|
|
attachment="-A ${chain}"
|
|
else
|
|
if [ ${positive_rule_number} -gt 1 ]
|
|
then
|
|
attachment="-I ${chain} ${positive_rule_number}"
|
|
else
|
|
attachment="-I ${chain}"
|
|
fi
|
|
fi
|
|
|
|
# ipvall
|
|
for ipv in ${ipvall}
|
|
do
|
|
case "${ipv}" in
|
|
ipv4)
|
|
iptables="iptables"
|
|
src="${src4[*]}"
|
|
dst="${dst4[*]}"
|
|
;;
|
|
|
|
ipv6)
|
|
iptables="ip6tables"
|
|
src="${src6[*]}"
|
|
dst="${dst6[*]}"
|
|
;;
|
|
esac
|
|
|
|
# uid
|
|
for tuid in ${uid[*]}
|
|
do
|
|
case ${tuid} in
|
|
any|ANY)
|
|
uid_arg=()
|
|
owner_arg=()
|
|
;;
|
|
|
|
*)
|
|
owner_arg=("-m" "owner")
|
|
uid_arg=(${uidnot} "--uid-owner" "${tuid}")
|
|
;;
|
|
esac
|
|
|
|
# gid
|
|
for tgid in ${gid[*]}
|
|
do
|
|
case ${tgid} in
|
|
any|ANY)
|
|
gid_arg=()
|
|
# do not reset owner_arg=() here
|
|
;;
|
|
|
|
*)
|
|
owner_arg=("-m" "owner")
|
|
gid_arg=(${gidnot} "--gid-owner" "${tgid}")
|
|
;;
|
|
esac
|
|
|
|
# mark
|
|
for tmark in ${mark[*]}
|
|
do
|
|
case ${tmark} in
|
|
any|ANY)
|
|
mark_arg=()
|
|
;;
|
|
|
|
*)
|
|
mark_arg=("-m" "mark" ${marknot} "--mark" "${tmark}")
|
|
;;
|
|
esac
|
|
|
|
# tos
|
|
for ttos in ${tos[*]}
|
|
do
|
|
case ${ttos} in
|
|
any|ANY)
|
|
tos_arg=()
|
|
;;
|
|
|
|
*)
|
|
tos_arg=("-m" "tos" ${tosnot} "--tos" "${ttos}")
|
|
;;
|
|
esac
|
|
|
|
# dscp
|
|
for tdscp in ${dscp[*]}
|
|
do
|
|
case ${tdscp} in
|
|
any|ANY)
|
|
dscp_arg=()
|
|
;;
|
|
|
|
*)
|
|
dscp_arg=("-m" "dscp" ${dscpnot} "--dscp${dscptype}" "${tdscp}")
|
|
;;
|
|
esac
|
|
|
|
# proto
|
|
for pr in ${proto[*]}
|
|
do
|
|
case ${pr} in
|
|
any|ANY)
|
|
proto_arg=()
|
|
;;
|
|
|
|
*)
|
|
proto_arg=(${protonot} "-p" "${pr}")
|
|
;;
|
|
esac
|
|
|
|
# inface
|
|
for inf in ${inface[*]}
|
|
do
|
|
case ${inf} in
|
|
any|ANY)
|
|
inf_arg=()
|
|
;;
|
|
|
|
*)
|
|
inf_arg=(${infacenot} "-i" "${inf}")
|
|
;;
|
|
esac
|
|
|
|
# outface
|
|
for outf in ${outface[*]}
|
|
do
|
|
case ${outf} in
|
|
any|ANY)
|
|
outf_arg=()
|
|
;;
|
|
|
|
*)
|
|
outf_arg=(${outfacenot} "-o" "${outf}")
|
|
;;
|
|
esac
|
|
|
|
# physin
|
|
for inph in ${physin[*]}
|
|
do
|
|
case ${inph} in
|
|
any|ANY)
|
|
inph_arg=()
|
|
physdev_arg=()
|
|
;;
|
|
|
|
*)
|
|
physdev_arg=("-m" "physdev" ${physbridge})
|
|
inph_arg=(${physinnot} "--physdev-in" "${inph}")
|
|
;;
|
|
esac
|
|
|
|
# physout
|
|
for outph in ${physout[*]}
|
|
do
|
|
case ${outph} in
|
|
any|ANY)
|
|
outph_arg=()
|
|
# do not reset physdev_arg=() here
|
|
;;
|
|
|
|
*)
|
|
physdev_arg=("-m" "physdev" ${physbridge})
|
|
outph_arg=(${physoutnot} "--physdev-out" "${outph}")
|
|
;;
|
|
esac
|
|
|
|
# mac
|
|
for mc in ${mac[*]}
|
|
do
|
|
case ${mc} in
|
|
any|ANY)
|
|
mc_arg=()
|
|
;;
|
|
|
|
*)
|
|
mc_arg=("-m" "mac" ${macnot} "--mac-source" "${mc}")
|
|
;;
|
|
esac
|
|
|
|
# src
|
|
for s in ${src}
|
|
do
|
|
case ${s} in
|
|
any|ANY)
|
|
s_arg=()
|
|
;;
|
|
|
|
ipset:*)
|
|
[ ${IPSET_WARNING} -eq 1 ] && ipset_warning
|
|
[ ${IPRANGE_WARNING} -eq 1 ] && iprange_warning
|
|
s="${s/ipset:/}"
|
|
test -z "${FIREHOL_IPSETS_USED[$s]}" && FIREHOL_IPSETS_USED[$s]="USED"
|
|
s_arg=("-m" "set" ${srcnot} "--match-set" "${s}" "src" ${IPSET_SRC_DST_OPTIONS})
|
|
;;
|
|
|
|
*:*-*|[0-9]*-*)
|
|
s_arg=("-m" "iprange" ${srcnot} "--src-range" "${s}")
|
|
;;
|
|
|
|
*)
|
|
s_arg=(${srcnot} "-s" "${s}")
|
|
;;
|
|
esac
|
|
|
|
# dst
|
|
for d in ${dst}
|
|
do
|
|
case ${d} in
|
|
any|ANY)
|
|
d_arg=()
|
|
;;
|
|
|
|
ipset:*)
|
|
[ ${IPSET_WARNING} -eq 1 ] && ipset_warning
|
|
[ ${IPRANGE_WARNING} -eq 1 ] && iprange_warning
|
|
d="${d/ipset:/}"
|
|
test -z "${FIREHOL_IPSETS_USED[$d]}" && FIREHOL_IPSETS_USED[$d]="USED"
|
|
d_arg=("-m" "set" ${dstnot} "--match-set" "${d}" "dst" ${IPSET_SRC_DST_OPTIONS})
|
|
;;
|
|
|
|
*:*-*|[0-9]*-*)
|
|
d_arg=("-m" "iprange" ${dstnot} "--dst-range" "${d}")
|
|
;;
|
|
|
|
*)
|
|
d_arg=(${dstnot} "-d" "${d}")
|
|
;;
|
|
esac
|
|
|
|
# sport
|
|
for sp in ${sport[*]}
|
|
do
|
|
case ${sp} in
|
|
any|ANY)
|
|
sp_arg=()
|
|
;;
|
|
|
|
multiport:*)
|
|
sp="${sp/multiport:/}"
|
|
sp_arg=("-m" "multiport" ${sportnot} "--source-ports" "${sp//-/:}")
|
|
;;
|
|
|
|
*)
|
|
sp_arg=(${sportnot} "--sport" "${sp//-/:}")
|
|
;;
|
|
esac
|
|
|
|
# dport
|
|
for dp in ${dport[*]}
|
|
do
|
|
case ${dp} in
|
|
any|ANY)
|
|
dp_arg=()
|
|
;;
|
|
|
|
multiport:*)
|
|
dp="${dp/multiport:/}"
|
|
dp_arg=("-m" "multiport" ${dportnot} "--destination-ports" "${dp//-/:}")
|
|
;;
|
|
|
|
*)
|
|
dp_arg=(${dportnot} "--dport" "${dp//-/:}")
|
|
;;
|
|
esac
|
|
|
|
# if there is sport or dport with multiport
|
|
# we have to give the multiport second
|
|
if [ "${sp_arg[1]}" = "multiport" ]
|
|
then
|
|
sp_arg=("${dp_arg[@]}" "${sp_arg[@]}")
|
|
dp_arg=()
|
|
elif [ "${dp_arg[1]}" = "multiport" ]
|
|
then
|
|
dp_arg=("${sp_arg[@]}" "${dp_arg[@]}")
|
|
sp_arg=()
|
|
fi
|
|
|
|
# build the command
|
|
basecmd=("${inf_arg[@]}" "${outf_arg[@]}" "${physdev_arg[@]}" "${inph_arg[@]}" "${outph_arg[@]}" "${proto_arg[@]}" \
|
|
"${s_arg[@]}" "${sp_arg[@]}" "${d_arg[@]}" "${dp_arg[@]}" "${mc_arg[@]}" "${tos_arg[@]}" \
|
|
"${owner_arg[@]}" "${uid_arg[@]}" "${gid_arg[@]}" \
|
|
"${addrtype_arg[@]}" "${stp_arg[@]}" "${dtp_arg[@]}" "${state_arg[@]}" "${mark_arg[@]}" "${dscp_arg[@]}" \
|
|
"${protected_args[@]}" "${positive_args[@]}")
|
|
|
|
# if we append rules
|
|
if [ -z "${positive_rule_number}" ]
|
|
then
|
|
if [ "$logrule" = "limit" ]; then ${iptables} -t ${table} ${attachment} "${basecmd[@]}" -m limit --limit "${FIREHOL_LOG_FREQUENCY}" --limit-burst "${FIREHOL_LOG_BURST}" -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}" || failed=$[failed + 1]; fi
|
|
if [ "$logrule" = "normal" ]; then ${iptables} -t ${table} ${attachment} "${basecmd[@]}" -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}" || failed=$[failed + 1]; fi
|
|
if [ ! -z "${accounting}" ]; then ${iptables} -t ${table} ${attachment} "${basecmd[@]}" -m nfacct --nfacct-name "${accounting}" || failed=$[failed + 1]; fi
|
|
fi
|
|
|
|
# do it!
|
|
rule_action_param ${iptables} "${action}" "${statenot}" "${state}" "${table}" "${connlog}" "${action_param[@]}" -- -t ${table} ${attachment} "${basecmd[@]}" || failed=$[failed + 1]
|
|
(( FIREHOL_RULE_POSITIVE_STATEMENTS_GENERATED += 1 ))
|
|
|
|
# if we insert rules
|
|
# we insert these at the same position we inserted the actual rule
|
|
# so it will be inserted before the actual rule
|
|
if [ ! -z "${positive_rule_number}" ]
|
|
then
|
|
if [ "$logrule" = "limit" ]; then ${iptables} -t ${table} ${attachment} "${basecmd[@]}" -m limit --limit "${FIREHOL_LOG_FREQUENCY}" --limit-burst "${FIREHOL_LOG_BURST}" -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}" || failed=$[failed + 1]; fi
|
|
if [ "$logrule" = "normal" ]; then ${iptables} -t ${table} ${attachment} "${basecmd[@]}" -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}" || failed=$[failed + 1]; fi
|
|
if [ ! -z "${accounting}" ]; then ${iptables} -t ${table} ${attachment} "${basecmd[@]}" -m nfacct --nfacct-name "${accounting}" || failed=$[failed + 1]; fi
|
|
fi
|
|
|
|
done # dport
|
|
done # sport
|
|
done # dst
|
|
done # src
|
|
done # mac
|
|
done # physout
|
|
done # physin
|
|
done # outface
|
|
done # inface
|
|
done # proto
|
|
done # dscp
|
|
done # tos
|
|
done # mark
|
|
done # gid
|
|
done # uid
|
|
done # ipvall
|
|
|
|
test ${failed} -gt 0 && error "There are ${failed} failed commands." && return 1
|
|
return 0
|
|
}
|
|
|
|
warning() {
|
|
test ${PROGRAM_SPINNER_RUNNING} -eq 1 && spinner_end
|
|
|
|
echo >&2
|
|
echo >&2 -e "${COLOR_YELLOW}WARNING${COLOR_RESET} ${COLOR_CYAN}${LAST_CONFIG_LINE}${COLOR_RESET}: " "${@}"
|
|
echo >&2
|
|
|
|
return 0
|
|
}
|
|
|
|
softwarning() {
|
|
test ${PROGRAM_SPINNER_RUNNING} -eq 1 && spinner_end
|
|
echo >&2
|
|
echo >&2 -e "--------------------------------------------------------------------------------"
|
|
echo >&2 -e "${COLOR_BOLD}${COLOR_YELLOW}WARNING${COLOR_RESET}"
|
|
echo >&2 -e "WHEN : ${work_function}"
|
|
echo >&2 -e "WHY : ${COLOR_BOLD}${COLOR_YELLOW}${@}${COLOR_RESET}"
|
|
printf >&2 "COMMAND: ${COLOR_YELLOW}"; printf >&2 "%q " "${work_realcmd[@]}"; echo >&2
|
|
echo >&2 -e "${COLOR_RESET}MODE :" "${FIREHOL_NS_CURR}"
|
|
echo >&2 -e "SOURCE : $(config_line)"
|
|
echo >&2
|
|
|
|
return 0
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# error - error reporting while still parsing the configuration file
|
|
# WHY:
|
|
# This is the error handler that presents to the user detected errors during
|
|
# processing FireHOL's configuration file.
|
|
# This command is directly called by other functions of FireHOL.
|
|
|
|
error() {
|
|
test ${PROGRAM_SPINNER_RUNNING} -eq 1 && spinner_end
|
|
test "${FIREHOL_MODE}" = "START" && syslog err "Error '${@}' when '${work_function}' $(config_line)"
|
|
|
|
work_error=$[work_error + 1]
|
|
echo >&2
|
|
echo >&2 -e "--------------------------------------------------------------------------------"
|
|
echo >&2 -e "${COLOR_BOLD}${COLOR_BGRED}${COLOR_WHITE} ERROR ${COLOR_RESET}: # ${work_error}"
|
|
echo >&2 -e "WHEN : ${work_function}"
|
|
echo >&2 -e "WHY : ${COLOR_BGRED}${COLOR_WHITE} ${@} ${COLOR_RESET}"
|
|
printf >&2 "COMMAND: ${COLOR_YELLOW}"; printf >&2 "%q " "${work_realcmd[@]}"; echo >&2
|
|
echo >&2 -e "${COLOR_RESET}MODE :" "${FIREHOL_NS_CURR}"
|
|
echo >&2 -e "SOURCE : $(config_line)"
|
|
echo >&2
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# runtime_error - postprocessing evaluation of commands run
|
|
# WHY:
|
|
# The generated iptables commands must be checked for errors in case they fail.
|
|
# This command is executed after every postprocessing command to find out
|
|
# if it has been successful or failed.
|
|
|
|
runtime_error() {
|
|
local type="ERROR" id=
|
|
|
|
case "${1}" in
|
|
error)
|
|
type="ERROR "
|
|
work_runtime_error=$[work_runtime_error + 1]
|
|
id="# ${work_runtime_error}."
|
|
;;
|
|
|
|
warn)
|
|
type="WARNING"
|
|
id="This might or might not affect the operation of your firewall."
|
|
;;
|
|
|
|
*)
|
|
work_runtime_error=$[work_runtime_error + 1]
|
|
id="# ${work_runtime_error}."
|
|
|
|
echo >&2
|
|
echo >&2
|
|
echo >&2 "WARNING: unsupported final status type '${1}'. Assuming it is 'ERROR'"
|
|
echo >&2
|
|
echo >&2
|
|
;;
|
|
esac
|
|
shift
|
|
|
|
local ret="${1}" line="${2}"
|
|
shift 2
|
|
|
|
syslog err "Runtime ${type} '${id}'. Source ${line}"
|
|
|
|
echo >&2
|
|
echo >&2
|
|
echo >&2 "--------------------------------------------------------------------------------"
|
|
echo >&2 "${type} : ${id}"
|
|
echo >&2 "WHAT : A runtime command failed to execute (returned error ${ret})."
|
|
echo >&2 "SOURCE : ${line}"
|
|
printf >&2 "COMMAND : "
|
|
printf >&2 "%q " "${@}"
|
|
printf >&2 "\n"
|
|
echo >&2 "OUTPUT : "
|
|
echo >&2
|
|
${CAT_CMD} ${FIREHOL_OUTPUT}.log >&2
|
|
echo >&2
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# chain_exists - find if chain name has already being specified
|
|
# WHY:
|
|
# We have to make sure each service gets its own chain.
|
|
# Although FireHOL chain naming makes chains with unique names, this is just
|
|
# an extra sanity check.
|
|
|
|
declare -A FIREHOL_NFACCT=()
|
|
declare -A FIREHOL_CHAINS=()
|
|
chain_exists() {
|
|
local table="${1}" chain="${2}"
|
|
|
|
if running_ipv4; then
|
|
test ! -z "${FIREHOL_CHAINS[${table}.${chain}.4]}" && return 1
|
|
# test -f "${FIREHOL_CHAINS_DIR}/${chain}.4" && return 1
|
|
fi
|
|
if running_ipv6; then
|
|
test ! -z "${FIREHOL_CHAINS[${table}.${chain}.6]}" && return 1
|
|
# test -f "${FIREHOL_CHAINS_DIR}/${chain}.6" && return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# create_chain - create a chain and link it to the firewall
|
|
# WHY:
|
|
# When a chain is created it must somehow to be linked to the rest of the
|
|
# firewall appropriately. This function first creates the chain and then
|
|
# it links it to its final position within the generated firewall.
|
|
|
|
# An associative array to hold chain aliases
|
|
# it is used to prevent creating unnecessary chain, while allowing
|
|
# simple and complex services to create virtual chains.
|
|
declare -A FIREHOL_CHAIN_ALIASES=()
|
|
|
|
create_chain() {
|
|
# echo >&2 "${FUNCNAME} ${*}"
|
|
local doalias=0 table= newchain= oldchain=
|
|
|
|
# requested an alias
|
|
if [ "${1}" = "alias" ]
|
|
then
|
|
[ ${FIREHOL_CHAIN_PER_SERVICE} -eq 0 ] && doalias=1
|
|
shift
|
|
fi
|
|
|
|
table="${1}"
|
|
newchain="${2}"
|
|
oldchain="${3}"
|
|
shift 3
|
|
|
|
# we have to jump to the new chain
|
|
# cannot do it with an alias
|
|
test ! -z "${*}" && doalias=0
|
|
|
|
set_work_function "Creating chain '${newchain}' under '${oldchain}' in table '${table}'"
|
|
|
|
chain_exists "${table}" "${newchain}"
|
|
test $? -eq 1 && error "Chain '${newchain}' already exists." && return 1
|
|
|
|
if [ ${doalias} -eq 1 ]
|
|
then
|
|
FIREHOL_CHAIN_ALIASES[$table.$newchain]="${oldchain}"
|
|
else
|
|
iptables_both -t ${table} -N "${newchain}" || return 1
|
|
|
|
running_ipv4 && FIREHOL_CHAINS[${table}.${newchain}.4]="1"
|
|
running_ipv6 && FIREHOL_CHAINS[${table}.${newchain}.6]="1"
|
|
|
|
if [ ! -z "${oldchain}" ]
|
|
then
|
|
rule table ${table} chain "${oldchain}" action "${newchain}" "${@}" || return 1
|
|
fi
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# smart_function - find the valid service definition for a service
|
|
# WHY:
|
|
# FireHOL supports simple and complex services. This function first tries to
|
|
# detect if there are the proper variables set for a simple service, and if
|
|
# they do not exist, it then tries to find the complex function definition for
|
|
# the service.
|
|
#
|
|
# Additionally, it creates a chain for the subcommand.
|
|
|
|
ALL_SHOULD_ALSO_RUN_WARNING=0
|
|
smart_function() {
|
|
local type="${1}" services=(${2//,/ }) service= servname= suffix= mychain= ret= fn= dogroup=0 reverse= added=0
|
|
# type = the current subcommand: server/client/route
|
|
# services = the services to implement
|
|
shift 2
|
|
|
|
# if [ "${#services[*]}" -gt 1 -a ! -z "${*}" ]
|
|
# then
|
|
# dogroup=1
|
|
# [ "${type}" = "client" ] && reverse="reverse"
|
|
# # create a new chain for their target
|
|
# # add all args to it
|
|
# # change the action with it for all services
|
|
# shift "${#@}"
|
|
# fi
|
|
|
|
for service in ${services[@]}
|
|
do
|
|
servname="${service}"
|
|
test "${service}" = "custom" && servname="${1}"
|
|
|
|
if [ "${service}" = "all" -a ! -z "${ALL_SHOULD_ALSO_RUN}" -a ${ALL_SHOULD_ALSO_RUN_WARNING} -eq 0 ]
|
|
then
|
|
ALL_SHOULD_ALSO_RUN_WARNING=1
|
|
warning "ALL_SHOULD_ALSO_RUN has been deprecated. Service 'all' now runs all these conntrack helpers: helper_all='${helper_all}'"
|
|
fi
|
|
|
|
set_work_function "Preparing for service '${service}' of type '${type}' under interface '${work_name}'"
|
|
|
|
# Increase the command counter, to make all chains within a primary
|
|
# command, unique.
|
|
local work_counter
|
|
get_next_work_counter work_counter
|
|
|
|
suffix="u${work_counter}"
|
|
case "${type}" in
|
|
client)
|
|
suffix="c${work_counter}"
|
|
;;
|
|
|
|
server)
|
|
suffix="s${work_counter}"
|
|
;;
|
|
|
|
route)
|
|
suffix="r${work_counter}"
|
|
;;
|
|
|
|
*) error "Cannot understand type '${type}'."
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
mychain="${work_name}_${servname}_${suffix}"
|
|
|
|
create_chain alias filter "in_${mychain}" "in_${work_name}" || return 1
|
|
create_chain alias filter "out_${mychain}" "out_${work_name}" || return 1
|
|
|
|
# Create it in the raw table for the reconstruction to work
|
|
if [ "${FIREHOL_CONNTRACK_HELPERS_ASSIGNMENT}" = "firehol" ]
|
|
then
|
|
create_chain alias raw "in_${mychain}" "in_${work_name}" || return 1
|
|
create_chain alias raw "out_${mychain}" "out_${work_name}" || return 1
|
|
fi
|
|
|
|
# Try the simple services first
|
|
simple_service "${mychain}" "${type}" "${service}" "${@}" "${FIREHOL_RULESET_MODE}"
|
|
ret=$?
|
|
|
|
# simple service completed succesfully.
|
|
test $ret -eq 0 && added=$((added + 1)) && continue
|
|
|
|
# simple service exists but failed.
|
|
if [ $ret -ne 127 ]
|
|
then
|
|
error "Simple service '${service}' returned an error ($ret)."
|
|
return 1
|
|
fi
|
|
|
|
|
|
# Try the custom services
|
|
fn="rules_${service}"
|
|
|
|
set_work_function "Complex rules for ${fn}() for ${type} '${service}'"
|
|
|
|
"${fn}" "${mychain}" "${type}" "${@}" "${FIREHOL_RULESET_MODE}"
|
|
ret=$?
|
|
test $ret -eq 0 && added=$((added + 1)) && continue
|
|
|
|
if [ $ret -eq 127 ]
|
|
then
|
|
error "There is no service '${service}' defined."
|
|
else
|
|
error "Complex service '${service}' returned an error ($ret)."
|
|
fi
|
|
return 1
|
|
done
|
|
|
|
if [ ${added} -eq 0 ]
|
|
then
|
|
error "No service added - probably a bad variable expansion."
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# simple_service - convert a service definition to an inline service definition
|
|
# WHY:
|
|
# When a simple service is detected, there must be someone to call
|
|
# rules_custom() with the appropriate service definition parameters.
|
|
|
|
declare -A FIREHOL_SERVER_PORTS_CACHE=()
|
|
declare -A FIREHOL_CLIENT_PORTS_CACHE=()
|
|
declare -A FIREHOL_SERVER_HELPERS_CACHE=()
|
|
declare -A FIREHOL_SERVER_MODS_CACHE=()
|
|
declare -A FIREHOL_SERVER_MODS_NAT_CACHE=()
|
|
|
|
simple_service() {
|
|
local mychain="${1}" \
|
|
type="${2}" \
|
|
server="${3}" \
|
|
server_varname= server_ports= \
|
|
client_varname= client_ports= \
|
|
varname= helpers= \
|
|
modules= modules_nat= \
|
|
x=
|
|
shift 3
|
|
|
|
server_ports="${FIREHOL_SERVER_PORTS_CACHE[$server]}"
|
|
if [ ! -z "${server_ports}" ]
|
|
then
|
|
[ "${server_ports}" = "127" ] && return 127
|
|
|
|
client_ports="${FIREHOL_CLIENT_PORTS_CACHE[$server]}"
|
|
helpers="${FIREHOL_SERVER_HELPERS_CACHE[$server]}"
|
|
modules="${FIREHOL_SERVER_MODS_CACHE[$server]}"
|
|
modules_nat="${FIREHOL_SERVER_MODS_NAT_CACHE[$server]}"
|
|
else
|
|
server_varname="server_${server}_ports"
|
|
eval server_ports="\$${server_varname}"
|
|
|
|
client_varname="client_${server}_ports"
|
|
eval client_ports="\$${client_varname}"
|
|
|
|
varname="helper_${server}"
|
|
eval helpers="\$${varname}"
|
|
|
|
varname="require_${server}_modules"
|
|
eval modules="\$${varname}"
|
|
|
|
varname="require_${server}_nat_modules"
|
|
eval modules_nat="\$${varname}"
|
|
|
|
FIREHOL_SERVER_PORTS_CACHE[$server]="${server_ports}"
|
|
FIREHOL_CLIENT_PORTS_CACHE[$server]="${client_ports}"
|
|
FIREHOL_SERVER_HELPERS_CACHE[$server]="${helpers}"
|
|
FIREHOL_SERVER_MODS_CACHE[$server]="${modules}"
|
|
FIREHOL_SERVER_MODS_NAT_CACHE[$server]="${modules_nat}"
|
|
|
|
if [ ! -z "${server_ports}" -a -z "${client_ports}" ]
|
|
then
|
|
error "Simple service '${service}' has server ports, but no client ports defined."
|
|
return 1
|
|
elif [ -z "${server_ports}" -a ! -z "${client_ports}" ]
|
|
then
|
|
error "Simple service '${service}' has client ports, but no server ports defined."
|
|
return 1
|
|
elif [ -z "${server_ports}" -a -z "${client_ports}" ]
|
|
then
|
|
# this will make the caller attempt to find a complex service
|
|
FIREHOL_SERVER_PORTS_CACHE[$server]="127"
|
|
return 127
|
|
fi
|
|
|
|
for x in ${modules}
|
|
do
|
|
require_kernel_module $x || return 1
|
|
done
|
|
|
|
if [ ${FIREHOL_NAT} -eq 1 ]
|
|
then
|
|
for x in ${modules_nat}
|
|
do
|
|
require_kernel_module $x || return 1
|
|
done
|
|
fi
|
|
|
|
# load the helper modules
|
|
for x in ${helpers}
|
|
do
|
|
case "${x}" in
|
|
snmp_basic) # this does not exist in conntrack
|
|
;;
|
|
|
|
*) require_kernel_module nf_conntrack_$x
|
|
;;
|
|
esac
|
|
|
|
if [ ${FIREHOL_NAT} -eq 1 ]
|
|
then
|
|
case "${x}" in
|
|
netbios_ns|netlink|sane)
|
|
# these do not exist in nat
|
|
;;
|
|
|
|
*) require_kernel_module nf_nat_$x
|
|
;;
|
|
esac
|
|
fi
|
|
done
|
|
fi
|
|
|
|
set_work_function "Simple rules for ${type} '${service}'"
|
|
rules_custom "${mychain}" "${type}" "${server}" "${server_ports}" "${client_ports}" helpers "${helpers}" "${@}"
|
|
|
|
return $?
|
|
}
|
|
|
|
show_work_realcmd() {
|
|
test "${FIREHOL_MODE}" = "EXPLAIN" && return 0
|
|
|
|
(
|
|
printf "\n\n"
|
|
printf "# === CONFIGURATION STATEMENT =================================================\n"
|
|
printf "# $(config_line)\n"
|
|
printf "# >>> "
|
|
|
|
case $1 in
|
|
2) printf " "
|
|
;;
|
|
*) ;;
|
|
esac
|
|
|
|
printf "%q " "${work_realcmd[@]}"
|
|
printf "\n\n"
|
|
) >&21
|
|
}
|
|
|
|
FIREHOL_FILTERING_STARTED=0
|
|
work_realcmd_primary() {
|
|
test -z "${FIREHOL_DEFAULT_NAMESPACE}" && init_namespace
|
|
test ${FIREHOL_ENABLE_SPINNER} -eq 1 && spinner ${FIREHOL_COMMAND_COUNTER}
|
|
|
|
if [ ${FIREHOL_FILTERING_STARTED} -eq 0 ]
|
|
then
|
|
FIREHOL_FILTERING_STARTED=1
|
|
firewall_filtering_policy
|
|
fi
|
|
|
|
config_line -ne
|
|
work_realcmd=("${@}")
|
|
test ${FIREHOL_CONF_SHOW} -eq 1 && show_work_realcmd 1
|
|
|
|
# echo >&2 "CMD: ${@}: NS STACK: ${FIREHOL_NS_STACK[@]}"
|
|
}
|
|
|
|
work_realcmd_secondary() {
|
|
test -z "${FIREHOL_DEFAULT_NAMESPACE}" && init_namespace
|
|
test ${FIREHOL_ENABLE_SPINNER} -eq 1 && spinner ${FIREHOL_COMMAND_COUNTER}
|
|
|
|
config_line -ne
|
|
work_realcmd=("${@}")
|
|
test ${FIREHOL_CONF_SHOW} -eq 1 && show_work_realcmd 2
|
|
|
|
# echo >&2 "CMD: ${@}: NS STACK: ${FIREHOL_NS_STACK[@]}"
|
|
}
|
|
|
|
work_realcmd_helper() {
|
|
test -z "${FIREHOL_DEFAULT_NAMESPACE}" && init_namespace
|
|
test ${FIREHOL_ENABLE_SPINNER} -eq 1 && spinner ${FIREHOL_COMMAND_COUNTER}
|
|
|
|
config_line -ne
|
|
work_realcmd=("${@}")
|
|
test ${FIREHOL_CONF_SHOW} -eq 1 && show_work_realcmd 3
|
|
|
|
# echo >&2 "CMD: ${@}: NS STACK: ${FIREHOL_NS_STACK[@]}"
|
|
}
|
|
|
|
wait_for_interface() {
|
|
local iface="${1}" timeout=60 found=0 start=`$DATE_CMD +%s` addr=
|
|
shift
|
|
|
|
[ -n "$1" ] && timeout="${1}"
|
|
|
|
while [ "`$DATE_CMD +%s`" -lt $(($start+$timeout)) -a $found -eq 0 ]
|
|
do
|
|
addr=`$IP_CMD addr show $iface 2> /dev/null | ${SED_CMD} -n 's/^ *inet \([^ ]*\).*/\1/p'`
|
|
[ -n "$addr" ] && found=1
|
|
[ $found -eq 0 ] && $SLEEP_CMD 0.5
|
|
done
|
|
|
|
# the interface is up
|
|
[ $found -eq 1 ] && return 0
|
|
|
|
return 1
|
|
}
|
|
|
|
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# keep a copy of the running firewall on disk for fast restoration
|
|
|
|
fixed_save() {
|
|
local command="$1" tmp="${FIREHOL_DIR}/iptables-save-$$" err=
|
|
|
|
load_kernel_module ip_tables
|
|
${command} -c >$tmp
|
|
err=$?
|
|
if [ ! $err -eq 0 ]
|
|
then
|
|
${RM_CMD} -f $tmp >/dev/null 2>&1
|
|
return $err
|
|
fi
|
|
|
|
${CAT_CMD} ${tmp} |\
|
|
${SED_CMD} \
|
|
-e "/#/s/ on .*/ on time removed by FireHOL/" \
|
|
-e "s/^\[[0-9][0-9]*:[[0-9][0-9]*\]/[0:0]/" \
|
|
-e "s/\[[0-9][0-9]*:[[0-9][0-9]*\]$/[0:0]/" \
|
|
-e "s/--uid-owner !/! --uid-owner /g" \
|
|
-e "s/--gid-owner !/! --gid-owner /g"
|
|
err=$?
|
|
|
|
${RM_CMD} -f $tmp >/dev/null 2>&1
|
|
return $err
|
|
}
|
|
|
|
FIREHOL_LAST_SUCCESSFUL_COMMAND="${FIREHOL_SPOOL_DIR}/firehol-last-ok-command"
|
|
firehol_save_activated_firewall() {
|
|
progress "Saving activated firewall to '${FIREHOL_SPOOL_DIR}'"
|
|
|
|
if [ -f "${FIREHOL_SPOOL_DIR}/ipv4.enable" ]
|
|
then
|
|
fixed_save ${IPTABLES_SAVE_CMD} >"${FIREHOL_SPOOL_DIR}/ipv4.rules"
|
|
if [ ! $? -eq 0 ]
|
|
then
|
|
failure # "Saving activated firewall to '${FIREHOL_SPOOL_DIR}'"
|
|
return 1
|
|
fi
|
|
|
|
${CHOWN_CMD} root:root "${FIREHOL_SPOOL_DIR}/ipv4.rules"
|
|
${CHMOD_CMD} 600 "${FIREHOL_SPOOL_DIR}/ipv4.rules"
|
|
else
|
|
test -f "${FIREHOL_SPOOL_DIR}/ipv4.rules" && $RM_CMD "${FIREHOL_SPOOL_DIR}/ipv4.rules"
|
|
fi
|
|
|
|
if [ -f "${FIREHOL_SPOOL_DIR}/ipv6.enable" ]
|
|
then
|
|
fixed_save ${IP6TABLES_SAVE_CMD} >"${FIREHOL_SPOOL_DIR}/ipv6.rules"
|
|
if [ ! $? -eq 0 ]
|
|
then
|
|
failure # "Saving activated firewall to '${FIREHOL_SPOOL_DIR}'"
|
|
return 1
|
|
fi
|
|
|
|
${CHOWN_CMD} root:root "${FIREHOL_SPOOL_DIR}/ipv6.rules"
|
|
${CHMOD_CMD} 600 "${FIREHOL_SPOOL_DIR}/ipv6.rules"
|
|
else
|
|
test -f "${FIREHOL_SPOOL_DIR}/ipv6.rules" && $RM_CMD "${FIREHOL_SPOOL_DIR}/ipv6.rules"
|
|
fi
|
|
|
|
printf "%q " "${FIREHOL_ARGS[@]}" >"${FIREHOL_LAST_SUCCESSFUL_COMMAND}"
|
|
printf "\n" >>"${FIREHOL_LAST_SUCCESSFUL_COMMAND}"
|
|
|
|
success # "Saving activated firewall to '${FIREHOL_SPOOL_DIR}'"
|
|
|
|
return 0
|
|
}
|
|
|
|
firehol_can_restore_saved_firewall() {
|
|
test ! -f "${FIREHOL_LAST_SUCCESSFUL_COMMAND}" \
|
|
&& warning "No saved firewall found to restore." \
|
|
&& return 1
|
|
|
|
local args="`printf "%q " "${FIREHOL_ARGS[@]}"`"
|
|
local old_args="`$CAT_CMD "${FIREHOL_LAST_SUCCESSFUL_COMMAND}"`"
|
|
test ! "${args}" = "${old_args}" \
|
|
&& warning "Saved firewall cannot be restored because it was run with different parameters." \
|
|
&& return 2
|
|
|
|
local do_ipv4=0 do_ipv6=0
|
|
test -f "${FIREHOL_SPOOL_DIR}/ipv4.enable" -a -f "${FIREHOL_SPOOL_DIR}/ipv4.rules" && do_ipv4=1
|
|
test -f "${FIREHOL_SPOOL_DIR}/ipv6.enable" -a -f "${FIREHOL_SPOOL_DIR}/ipv6.rules" && do_ipv6=1
|
|
test "${do_ipv4}${do_ipv6}" = "00" \
|
|
&& warning "Saved firewall includes neither IPv4 nor IPv6 rules to restore." \
|
|
&& return 1
|
|
|
|
test "${FIREHOL_CONFIG}" -nt "${FIREHOL_LAST_SUCCESSFUL_COMMAND}" \
|
|
&& warning "${FIREHOL_CONFIG} is newer than saved firewall. Cannot restore saved firewall." \
|
|
&& return 3
|
|
|
|
${FIND_CMD} "${FIREHOL_CONFIG_DIR}" "${FIREHOL_SERVICES_DIR}" -type f > "${FIREHOL_DIR}/config-list"
|
|
local f
|
|
while read f
|
|
do
|
|
# Not all "$FIND_CMD" have -newer but bash always has -nt ...
|
|
test "${f}" -nt "${FIREHOL_LAST_SUCCESSFUL_COMMAND}" \
|
|
&& warning "${f} is newer than saved firewall. Cannot restore saved firewall." \
|
|
&& return 4
|
|
done < "${FIREHOL_DIR}/config-list"
|
|
|
|
if [ ${ENABLE_IPSET} -eq 1 ]
|
|
then
|
|
ipsets_apply_all spool || return 6
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
firehol_restore_last_activated_firewall() {
|
|
firehol_can_restore_saved_firewall || return 2
|
|
|
|
progress "Restoring last activated firewall from '${FIREHOL_SPOOL_DIR}'"
|
|
|
|
if [ -x "${FIREHOL_SPOOL_DIR}/firewall_restore_commands.sh" ]
|
|
then
|
|
"${FIREHOL_SPOOL_DIR}/firewall_restore_commands.sh" >/dev/null
|
|
if [ $? -ne 0 ]
|
|
then
|
|
warning "Failed to execute restoration script."
|
|
failure # "Restoring last activated firewall from '${FIREHOL_SPOOL_DIR}'"
|
|
return 3
|
|
fi
|
|
fi
|
|
|
|
if [ -f "${FIREHOL_SPOOL_DIR}/ipv4.enable" -a -f "${FIREHOL_SPOOL_DIR}/ipv4.rules" ]
|
|
then
|
|
${IPTABLES_RESTORE_CMD} <"${FIREHOL_SPOOL_DIR}/ipv4.rules"
|
|
if [ $? -ne 0 ]
|
|
then
|
|
warning "Failed to restore IPv4 rules."
|
|
failure # "Restoring last activated firewall from '${FIREHOL_SPOOL_DIR}'"
|
|
return 3
|
|
fi
|
|
fi
|
|
|
|
if [ -f "${FIREHOL_SPOOL_DIR}/ipv6.enable" -a -f "${FIREHOL_SPOOL_DIR}/ipv6.rules" ]
|
|
then
|
|
${IP6TABLES_RESTORE_CMD} <"${FIREHOL_SPOOL_DIR}/ipv6.rules"
|
|
if [ $? -ne 0 ]
|
|
then
|
|
warning "Failed to restore IPv6 rules."
|
|
failure # "Restoring last activated firewall from '${FIREHOL_SPOOL_DIR}'"
|
|
return 3
|
|
fi
|
|
fi
|
|
|
|
success # "Saving activated firewall to '${FIREHOL_SPOOL_DIR}'"
|
|
return 0
|
|
}
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# START UP SCRIPT PROCESSING
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
|
|
kernel_maj_min() {
|
|
local kmaj kmin IFS=.-
|
|
|
|
kmaj=$1
|
|
kmin=$2
|
|
|
|
set -- $($UNAME_CMD -r)
|
|
eval $kmaj=\$1 $kmin=\$2
|
|
}
|
|
kernel_maj_min KERNELMAJ KERNELMIN
|
|
|
|
if [ "$KERNELMAJ" -lt 2 ] ; then
|
|
echo >&2 "FireHOL requires a kernel version higher than 2.3."
|
|
exit 0
|
|
fi
|
|
if [ "$KERNELMAJ" -eq 2 -a "$KERNELMIN" -lt 3 ] ; then
|
|
echo >&2 "FireHOL requires a kernel version higher than 2.3."
|
|
exit 0
|
|
fi
|
|
|
|
if ${LSMOD_CMD} 2>/dev/null | ${GREP_CMD} -q ipchains ; then
|
|
# Don't do both
|
|
echo >&2 "ipchains is loaded in the kernel. Please remove ipchains to run iptables."
|
|
exit 0
|
|
fi
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# COMMAND LINE ARGUMENTS PROCESSING
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
|
|
syslog info "FireHOL started from '$PWD' with: ${0} ${*}"
|
|
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
case "${1}" in
|
|
fast) FIREHOL_FAST_ACTIVATION=1
|
|
;;
|
|
|
|
nofast) FIREHOL_FAST_ACTIVATION=0
|
|
;;
|
|
|
|
optimal)
|
|
FIREHOL_RULESET_MODE="optimal"
|
|
;;
|
|
|
|
accurate)
|
|
FIREHOL_RULESET_MODE="accurate"
|
|
;;
|
|
|
|
reset-ipsets|reset_ipsets)
|
|
FIREHOL_IPSETS_RESPECT_KEEP=0
|
|
;;
|
|
|
|
*) break;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
arg="${1}"
|
|
shift
|
|
|
|
case "${arg}" in
|
|
explain)
|
|
test ! -z "${1}" && warning "Arguments after parameter '${arg}' are ignored."
|
|
FIREHOL_FILTERING_STARTED=1
|
|
FIREHOL_FAST_ACTIVATION=0
|
|
FIREHOL_MODE="EXPLAIN"
|
|
;;
|
|
|
|
helpme|wizard)
|
|
test ! -z "${1}" && warning "Arguments after parameter '${arg}' are ignored."
|
|
FIREHOL_MODE="WIZARD"
|
|
;;
|
|
|
|
try)
|
|
FIREHOL_MODE="START"
|
|
FIREHOL_TRY=1
|
|
;;
|
|
|
|
start)
|
|
FIREHOL_MODE="START"
|
|
FIREHOL_TRY=0
|
|
;;
|
|
|
|
stop)
|
|
FIREHOL_MODE="STOP"
|
|
test ! -z "${1}" && warning "Arguments after parameter '${arg}' are ignored."
|
|
|
|
progress "Clearing firewall"
|
|
if [ $ENABLE_IPV4 -eq 1 ]; then
|
|
load_kernel_module ip_tables
|
|
tables=`${CAT_CMD} /proc/net/ip_tables_names`
|
|
for t in ${tables}
|
|
do
|
|
${IPTABLES_CMD} -t "${t}" -F
|
|
${IPTABLES_CMD} -t "${t}" -X
|
|
${IPTABLES_CMD} -t "${t}" -Z
|
|
|
|
# Find all default chains in this table.
|
|
chains=`${IPTABLES_CMD} -t "${t}" -nL | ${GREP_CMD} "^Chain " | ${CUT_CMD} -d ' ' -f 2`
|
|
for c in ${chains}
|
|
do
|
|
${IPTABLES_CMD} -t "${t}" -P "${c}" ACCEPT
|
|
done
|
|
done
|
|
fi
|
|
if [ $ENABLE_IPV6 -eq 1 ]; then
|
|
load_kernel_module ip6_tables
|
|
tables6=`${CAT_CMD} /proc/net/ip6_tables_names`
|
|
for t in ${tables6}
|
|
do
|
|
${IP6TABLES_CMD} -t "${t}" -F
|
|
${IP6TABLES_CMD} -t "${t}" -X
|
|
${IP6TABLES_CMD} -t "${t}" -Z
|
|
|
|
# Find all default chains in this table.
|
|
chains=`${IP6TABLES_CMD} -t "${t}" -nL | ${GREP_CMD} "^Chain " | ${CUT_CMD} -d ' ' -f 2`
|
|
for c in ${chains}
|
|
do
|
|
${IP6TABLES_CMD} -t "${t}" -P "${c}" ACCEPT
|
|
done
|
|
done
|
|
fi
|
|
success # "Clearing firewall"
|
|
# Remove the saved firewall, so that the trap will not restore it.
|
|
${RM_CMD} -f "${FIREHOL_SAVED}" "${FIREHOL_SAVED6}" >/dev/null 2>&1
|
|
# signal the trap to exit with success
|
|
FIREHOL_ACTIVATED_SUCCESSFULLY=1
|
|
exit 0
|
|
;;
|
|
|
|
restore|condrestart)
|
|
FIREHOL_RESTORE_INSTEAD_OF_START=1
|
|
FIREHOL_MODE="START"
|
|
FIREHOL_TRY=0
|
|
;;
|
|
|
|
restart|force-reload)
|
|
FIREHOL_MODE="START"
|
|
FIREHOL_TRY=0
|
|
;;
|
|
|
|
cstatus|conntrack_status)
|
|
lnstat -c -1 -f nf_conntrack
|
|
exit $?
|
|
;;
|
|
|
|
sstatus|synproxy_status)
|
|
lnstat -c -1 -f synproxy
|
|
exit $?
|
|
;;
|
|
|
|
status)
|
|
test ! -z "${1}" && warning "Arguments after parameter '${arg}' are ignored."
|
|
(
|
|
if [ $ENABLE_IPV4 -eq 1 ]; then
|
|
echo
|
|
echo
|
|
echo "--- RAW IPv4 -------------------------------------------------------------------"
|
|
echo
|
|
${IPTABLES_CMD} -t raw -nxvL
|
|
fi
|
|
|
|
if [ $ENABLE_IPV6 -eq 1 ]; then
|
|
echo
|
|
echo
|
|
echo "--- RAW IPv6 -------------------------------------------------------------------"
|
|
echo
|
|
${IP6TABLES_CMD} -t raw -nxvL
|
|
fi
|
|
|
|
if [ $ENABLE_IPV4 -eq 1 ]; then
|
|
echo
|
|
echo
|
|
echo "--- MANGLE IPv4 ----------------------------------------------------------------"
|
|
echo
|
|
${IPTABLES_CMD} -t mangle -nxvL
|
|
fi
|
|
|
|
if [ $ENABLE_IPV6 -eq 1 ]; then
|
|
echo
|
|
echo
|
|
echo "--- MANGLE IPv6 ----------------------------------------------------------------"
|
|
echo
|
|
${IP6TABLES_CMD} -t mangle -nxvL
|
|
fi
|
|
|
|
if [ $ENABLE_IPV4 -eq 1 ]; then
|
|
echo
|
|
echo
|
|
echo "--- NAT IPv4 -------------------------------------------------------------------"
|
|
echo
|
|
${IPTABLES_CMD} -t nat -nxvL
|
|
fi
|
|
|
|
if [ $ENABLE_IPV6 -eq 1 ]; then
|
|
echo
|
|
echo
|
|
echo "--- NAT IPv6 -------------------------------------------------------------------"
|
|
echo
|
|
if $GREP_CMD -q '^nat$' /proc/net/ip6_tables_names
|
|
then
|
|
${IP6TABLES_CMD} -t nat -nxvL
|
|
else
|
|
echo "IPv6 NAT not available"
|
|
fi
|
|
fi
|
|
|
|
if [ $ENABLE_IPV4 -eq 1 ]; then
|
|
echo
|
|
echo
|
|
echo "--- FILTER IPv4 ----------------------------------------------------------------"
|
|
echo
|
|
${IPTABLES_CMD} -nxvL
|
|
fi
|
|
|
|
if [ $ENABLE_IPV6 -eq 1 ]; then
|
|
echo
|
|
echo
|
|
echo "--- FILTER IPv6 ----------------------------------------------------------------"
|
|
echo
|
|
${IP6TABLES_CMD} -nxvL
|
|
fi
|
|
|
|
if [ ${ENABLE_IPSET} -eq 1 ]; then
|
|
echo
|
|
echo
|
|
echo "--- IPSETs ---------------------------------------------------------------------"
|
|
echo
|
|
${IPSET_CMD} -L
|
|
fi
|
|
) >"${FIREHOL_DIR}/status"
|
|
${MORE_CMD} <"${FIREHOL_DIR}/status"
|
|
exit $?
|
|
;;
|
|
|
|
panic)
|
|
FIREHOL_MODE="PANIC"
|
|
ssh_src=
|
|
ssh_sport="0:65535"
|
|
ssh_dport="0:65535"
|
|
if [ ! -z "${SSH_CLIENT}" ]
|
|
then
|
|
set -- ${SSH_CLIENT}
|
|
ssh_src="${1}"
|
|
ssh_sport="${2}"
|
|
ssh_dport="${3}"
|
|
elif [ ! -z "${1}" ]
|
|
then
|
|
ssh_src="${1}"
|
|
fi
|
|
|
|
syslog info "Starting PANIC mode (SSH SOURCE_IP=${ssh_src} SOURCE_PORTS=${ssh_sport} DESTINATION_PORTS=${ssh_dport})"
|
|
progress "Blocking all communications"
|
|
if [ $ENABLE_IPV4 -eq 1 ]; then
|
|
load_kernel_module ip_tables
|
|
tables=`${CAT_CMD} /proc/net/ip_tables_names`
|
|
for t in ${tables}
|
|
do
|
|
${IPTABLES_CMD} -t "${t}" -F
|
|
${IPTABLES_CMD} -t "${t}" -X
|
|
${IPTABLES_CMD} -t "${t}" -Z
|
|
|
|
# Find all default chains in this table.
|
|
chains=`${IPTABLES_CMD} -t "${t}" -nL | ${GREP_CMD} "^Chain " | ${CUT_CMD} -d ' ' -f 2`
|
|
for c in ${chains}
|
|
do
|
|
${IPTABLES_CMD} -t "${t}" -P "${c}" ACCEPT
|
|
|
|
if [ ! -z "${ssh_src}" ]
|
|
then
|
|
${IPTABLES_CMD} -t "${t}" -A "${c}" -p tcp -s "${ssh_src}" --sport "${ssh_sport}" --dport "${ssh_dport}" -m conntrack --ctstate ESTABLISHED -j ACCEPT
|
|
${IPTABLES_CMD} -t "${t}" -A "${c}" -p tcp -d "${ssh_src}" --dport "${ssh_sport}" --sport "${ssh_dport}" -m conntrack --ctstate ESTABLISHED -j ACCEPT
|
|
fi
|
|
if [ "${t}" != "nat" ] ; then
|
|
${IPTABLES_CMD} -t "${t}" -A "${c}" -j DROP
|
|
fi
|
|
done
|
|
done
|
|
fi
|
|
if [ $ENABLE_IPV6 -eq 1 ]; then
|
|
load_kernel_module ip6_tables
|
|
tables6=`${CAT_CMD} /proc/net/ip6_tables_names`
|
|
for t in ${tables6}
|
|
do
|
|
${IP6TABLES_CMD} -t "${t}" -F
|
|
${IP6TABLES_CMD} -t "${t}" -X
|
|
${IP6TABLES_CMD} -t "${t}" -Z
|
|
|
|
# Find all default chains in this table.
|
|
chains=`${IP6TABLES_CMD} -t "${t}" -nL | ${GREP_CMD} "^Chain " | ${CUT_CMD} -d ' ' -f 2`
|
|
for c in ${chains}
|
|
do
|
|
${IP6TABLES_CMD} -t "${t}" -P "${c}" ACCEPT
|
|
|
|
if [ ! -z "${ssh_src}" ]
|
|
then
|
|
${IP6TABLES_CMD} -t "${t}" -A "${c}" -p tcp -s "${ssh_src}" --sport "${ssh_sport}" --dport "${ssh_dport}" -m conntrack --ctstate ESTABLISHED -j ACCEPT
|
|
${IP6TABLES_CMD} -t "${t}" -A "${c}" -p tcp -d "${ssh_src}" --dport "${ssh_sport}" --sport "${ssh_dport}" -m conntrack --ctstate ESTABLISHED -j ACCEPT
|
|
fi
|
|
if [ "${t}" != "nat" ] ; then
|
|
${IP6TABLES_CMD} -t "${t}" -A "${c}" -j DROP
|
|
fi
|
|
done
|
|
done
|
|
fi
|
|
success # "Blocking all communications"
|
|
exit 0
|
|
;;
|
|
|
|
save)
|
|
test ! -z "${1}" && test ${1} != "--" && softwarning "Arguments after parameter '${arg}' are ignored."
|
|
FIREHOL_MODE="START"
|
|
FIREHOL_SAVE=1
|
|
;;
|
|
|
|
debug)
|
|
test ! -z "${1}" && test ${1} != "--" && softwarning "Arguments after parameter '${arg}' are ignored."
|
|
FIREHOL_MODE="DEBUG"
|
|
;;
|
|
|
|
ipset_update_from_file)
|
|
if [ ${ENABLE_IPSET} -ne 1 ]
|
|
then
|
|
echo >&2 "ipset is not enabled. Is ipset installed?"
|
|
exit 1
|
|
fi
|
|
name="${1}"
|
|
shift
|
|
|
|
if [ -f "${FIREHOL_SPOOL_DIR}/ipsets.conf" ]
|
|
then
|
|
source "${FIREHOL_SPOOL_DIR}/ipsets.conf"
|
|
if [ $? -ne 0 ]
|
|
then
|
|
warning "Cannot load ${FIREHOL_SPOOL_DIR}/ipsets.conf"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [ -z "${FIREHOL_IPSETS_USED[$name]}" -o ! -f "${FIREHOL_SPOOL_DIR}/ipset.${name}.rules" ]
|
|
then
|
|
error "ipset ${name} is not configured by firehol. Cannot proceed."
|
|
exit 1
|
|
fi
|
|
|
|
found=0
|
|
for x in $( ipset_list_active_names )
|
|
do
|
|
[ "$x" = "${name}" ] && found=1 && break
|
|
done
|
|
|
|
if [ ${found} -eq 0 ]
|
|
then
|
|
error "ipset ${name} is not active in the system. Cannot proceed."
|
|
exit 1
|
|
fi
|
|
|
|
progress "Updating ipset ${name} with options: ${*}"
|
|
|
|
tmp=$(${MKTEMP_CMD} "${FIREHOL_DIR}/ipset-XXXXXXXXXX") || exit 1
|
|
|
|
# add the IPs from the file
|
|
ipset_addfile "${name}" "${@}" >>${tmp}
|
|
if [ $? -ne 0 ]
|
|
then
|
|
failure # "Updating ipset '${name}' with options: ${*}"
|
|
exit 1
|
|
fi
|
|
|
|
# lets rename it
|
|
ipset_apply "${name}" "${tmp}" swap
|
|
if [ $? -ne 0 ]
|
|
then
|
|
failure # "Updating ipset '${name}' with options: ${*}"
|
|
ipset_remove_all_tmp_sets
|
|
exit 1
|
|
fi
|
|
ipset_done_all_tmp_sets
|
|
|
|
# let the user know
|
|
success "$(( $($WC_CMD -l <"${tmp}"))) entries" # "Updating ipset '${name}' with options: ${*}"
|
|
|
|
# keep it for restoration
|
|
if [ -f "${FIREHOL_SPOOL_DIR}/ipset.${name}.rules" ]
|
|
then
|
|
progress "Keeping ipset '${name}' for later restoration"
|
|
$CP_CMD ${tmp} "${FIREHOL_SPOOL_DIR}/ipset.${name}.rules"
|
|
if [ $? -eq 0 ]
|
|
then
|
|
success
|
|
else
|
|
failure
|
|
fi
|
|
fi
|
|
|
|
# save the whole ipset to spool
|
|
ipset_save_active_to_spool
|
|
|
|
# make the exit handler exit with 0
|
|
FIREHOL_ACTIVATED_SUCCESSFULLY=1
|
|
exit 0
|
|
;;
|
|
|
|
*) if [ ! -z "${arg}" -a -f "${arg}" ]
|
|
then
|
|
FIREHOL_MODE="START"
|
|
FIREHOL_TRY=1
|
|
FIREHOL_CONFIG="${arg}"
|
|
arg="${1}"
|
|
test -z "${arg}" && arg="try"
|
|
|
|
case "${arg}" in
|
|
start)
|
|
FIREHOL_TRY=0
|
|
shift
|
|
;;
|
|
|
|
try)
|
|
FIREHOL_TRY=1
|
|
shift
|
|
;;
|
|
|
|
debug)
|
|
FIREHOL_MODE="DEBUG"
|
|
FIREHOL_TRY=0
|
|
shift
|
|
;;
|
|
|
|
restore|condrestart)
|
|
FIREHOL_RESTORE_INSTEAD_OF_START=1
|
|
FIREHOL_TRY=0
|
|
shift
|
|
;;
|
|
|
|
--) ;;
|
|
|
|
*)
|
|
echo >&2 "Cannot accept command line argument '${1}' here."
|
|
exit 1
|
|
;;
|
|
esac
|
|
else
|
|
|
|
test ! -z "${arg}" -a ! -f "${arg}" && echo >&2 "File '${arg}' not found."
|
|
|
|
emit_version
|
|
|
|
${CAT_CMD} <<EOF
|
|
|
|
FireHOL supports the following options (before any command):
|
|
|
|
fast sets FIREHOL_FAST_ACTIVATION=1
|
|
|
|
nofast sets FIREHOL_FAST_ACTIVATION=0
|
|
|
|
optimal sets FIREHOL_RULESET_MODE="optimal"
|
|
|
|
accurate sets FIREHOL_RULESET_MODE="accurate"
|
|
|
|
reset-ipsets removes and recreates all ipsets used.
|
|
dynamic ipsets (such as the ones created
|
|
by the iptrap helper), are not recreated
|
|
so that they will keep their data.
|
|
this option recreates them.
|
|
|
|
|
|
FireHOL supports the following commands (only one of them):
|
|
|
|
start to activate the firewall configuration.
|
|
The configuration is expected to be found in
|
|
${FIREHOL_CONFIG_DIR}/firehol.conf
|
|
|
|
try to activate the firewall, but wait until
|
|
the user types the word "commit". If this word
|
|
is not typed within 30 seconds, the previous
|
|
firewall is restored.
|
|
|
|
stop to stop a running iptables firewall.
|
|
This will allow all traffic to pass unchecked.
|
|
|
|
restart this is an alias for start and is given for
|
|
compatibility with /etc/init.d/iptables.
|
|
|
|
status will show the running firewall, as in:
|
|
${IPTABLES_CMD} -nxvL
|
|
and
|
|
${IP6TABLES_CMD} -nxvL
|
|
|
|
cstatus show connection tracker status
|
|
|
|
panic will block all IP communication.
|
|
|
|
restore will restore the last activated firewall.
|
|
Useful for quickly restoring at boot the last
|
|
successfully activated FireHOL firewall.
|
|
|
|
save to start the firewall and then save it to the
|
|
place where /etc/init.d/iptables looks for it.
|
|
|
|
Note that not all firewalls will work if
|
|
restored with:
|
|
/etc/init.d/iptables start
|
|
|
|
The fastest way to restore a FireHOL firewall
|
|
at boot is the 'restore' feature.
|
|
|
|
debug to parse the configuration file but instead of
|
|
activating it, to show the generated iptables
|
|
statements.
|
|
|
|
explain to enter interactive mode and accept configuration
|
|
directives. It also gives the iptables commands
|
|
for each directive together with reasoning.
|
|
|
|
helpme or to enter a wizard mode where FireHOL will try
|
|
wizard to figure out the configuration you need.
|
|
You can redirect the standard output of FireHOL to
|
|
a file to get the config to this file.
|
|
|
|
<a filename> a different configuration file.
|
|
If not other argument is given, the configuration
|
|
will be "tried" (default = try).
|
|
Otherwise the argument next to the filename can
|
|
be one of 'start', 'debug' and 'try'.
|
|
|
|
|
|
-------------------------------------------------------------------------
|
|
|
|
FireHOL supports the following services (sorted by name):
|
|
EOF
|
|
|
|
|
|
(
|
|
# The simple services
|
|
${CAT_CMD} "${PROGRAM_FILE}" |\
|
|
${GREP_CMD} -e "^server_.*_ports=" |\
|
|
${CUT_CMD} -d '=' -f 1 |\
|
|
${SED_CMD} "s/^server_//" |\
|
|
${SED_CMD} "s/_ports\$//"
|
|
|
|
# The complex services
|
|
${CAT_CMD} "${PROGRAM_FILE}" |\
|
|
${GREP_CMD} -e "^rules_.*()" |\
|
|
${CUT_CMD} -d '(' -f 1 |\
|
|
${SED_CMD} "s/^rules_/(*) /"
|
|
) | ${SORT_CMD} | ${UNIQ_CMD} |\
|
|
(
|
|
x=0
|
|
while read
|
|
do
|
|
x=$[x + 1]
|
|
if [ $x -gt 4 ]
|
|
then
|
|
printf "\n"
|
|
x=1
|
|
fi
|
|
printf "% 16s |" "$REPLY"
|
|
done
|
|
printf "\n\n"
|
|
)
|
|
|
|
${CAT_CMD} <<EOF
|
|
|
|
Services marked with (*) are "smart" or complex services.
|
|
All the others are simple single socket services.
|
|
|
|
Please note that the service:
|
|
|
|
all matches all packets and all protocols, while ensuring that
|
|
required kernel modules are loaded. Packets "untracked" by
|
|
iptables (e.g. ICMPv6 neighbour discovery packets) are not
|
|
included in "all" and must be handled separately.
|
|
|
|
any allows the matching of packets with unusual rules, like
|
|
only protocol but no ports. If service any is used
|
|
without other parameters, it does what service all does
|
|
but it does not handle kernel modules.
|
|
For example, to match GRE traffic use:
|
|
|
|
server any mygre accept proto 47
|
|
|
|
Service any does not handle kernel modules.
|
|
|
|
custom allows the definition of a custom service.
|
|
The template is:
|
|
|
|
server custom name protocol/sport cport accept
|
|
|
|
where name is just a name, protocol is the protocol the
|
|
service uses (tcp, udp, etc), sport is server port,
|
|
cport is the client port. For example, IMAP4 is:
|
|
|
|
server custom imap tcp/143 default accept
|
|
|
|
YOU DO NOT KNOW WHAT TO DO? FireHOL can help you! Just run it with the
|
|
argument 'helpme' and it will generate its configuration file for this
|
|
machine. Your running firewall will not be altered or stopped, and no
|
|
systems settings will be modified. Just run:
|
|
|
|
${PROGRAM_FILE} helpme >/tmp/firehol.conf
|
|
|
|
and you will get the configuration written to /tmp/firehol.conf
|
|
|
|
EOF
|
|
exit 1
|
|
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
# Remove all parameters until --
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
if [ "${1}" = "--" ]
|
|
then
|
|
shift
|
|
break
|
|
fi
|
|
|
|
warning "Parameter '${1}' is ignored."
|
|
shift
|
|
done
|
|
|
|
if [ "${FIREHOL_MODE}" = "START" -o "${FIREHOL_MODE}" = "DEBUG" ]
|
|
then
|
|
if [ ! -f "${FIREHOL_CONFIG}" ]
|
|
then
|
|
echo >&2 " ERROR: FireHOL config '${FIREHOL_CONFIG}' not found."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
test "${FIREHOL_MODE}" = "DEBUG" && FIREHOL_CONF_SHOW=1
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# MAIN PROCESSING - Interactive mode
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
|
|
if [ "${FIREHOL_MODE}" = "EXPLAIN" ]
|
|
then
|
|
FIREHOL_ENABLE_SPINNER=0
|
|
FIREHOL_CONF_SHOW=1
|
|
FIREHOL_FAST_ACTIVATION=0
|
|
FIREHOL_CONFIG="Interactive User Input"
|
|
lineid="1"
|
|
FORCE_CONFIG_LINEID="${lineid}@${FIREHOL_CONFIG}"
|
|
|
|
FIREHOL_TEMP_CONFIG="${FIREHOL_DIR}/firehol.conf"
|
|
|
|
echo "version ${FIREHOL_VERSION}" >"${FIREHOL_TEMP_CONFIG}"
|
|
version ${FIREHOL_VERSION}
|
|
|
|
emit_version
|
|
|
|
${CAT_CMD} <<EOF
|
|
You can now start typing firehol configuration directives.
|
|
Special interactive commands: help, show, quit
|
|
|
|
EOF
|
|
HISTFILE="${HOME}/.firehol_history"
|
|
test ! -f ${HISTFILE} && $TOUCH_CMD ${HISTFILE}
|
|
history -r
|
|
|
|
while [ 1 = 1 ]
|
|
do
|
|
# \001 = begin ignoring characters
|
|
# \002 = end ignoring characters
|
|
# without the above codes, lines do not wrap properly (readline counts also the color escape codes)
|
|
prompt="\001${COLOR_RESET}\002#\001${COLOR_GREEN}\002 FireHOL \001${COLOR_RESET}\002[\001${COLOR_BOLD}${COLOR_BLUE}\002${work_cmd}\001${COLOR_RESET}\002:\001${COLOR_CYAN}\002${work_name}\001${COLOR_RESET}\002] > "
|
|
eval "read -ep \$'${prompt}' -e -r REPLY"
|
|
test -z "${REPLY}" && continue
|
|
history -s "${REPLY}"
|
|
history -w
|
|
|
|
set_work_function -ne "Executing user input"
|
|
|
|
while [ 1 = 1 ]
|
|
do
|
|
|
|
set -- ${REPLY}
|
|
|
|
case "${1}" in
|
|
help)
|
|
${CAT_CMD} <<EOF
|
|
You can use anything a FireHOL configuration file accepts, including variables,
|
|
loops, etc. Take only care to write loops in one row.
|
|
|
|
Additionally, you can use the following commands:
|
|
|
|
help to print this text on your screen.
|
|
|
|
show to show all the successful commands so far.
|
|
|
|
quit to show the interactively given configuration file
|
|
and quit.
|
|
|
|
in|in4|in6
|
|
same as typing: interface(4|6) eth0 world
|
|
This is used as a shortcut to get into the server/client
|
|
mode in which you can test the rules for certain
|
|
services.
|
|
|
|
EOF
|
|
break
|
|
;;
|
|
|
|
show)
|
|
echo
|
|
${CAT_CMD} "${FIREHOL_TEMP_CONFIG}"
|
|
echo
|
|
break
|
|
;;
|
|
|
|
quit|exit)
|
|
echo
|
|
${CAT_CMD} "${FIREHOL_TEMP_CONFIG}"
|
|
echo
|
|
exit 1
|
|
;;
|
|
|
|
in)
|
|
REPLY="interface eth0 world"
|
|
continue
|
|
;;
|
|
|
|
in4)
|
|
REPLY="interface4 eth0 world"
|
|
continue
|
|
;;
|
|
|
|
in6)
|
|
REPLY="interface6 eth0 world"
|
|
continue
|
|
;;
|
|
|
|
*)
|
|
echo -e "# \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/"
|
|
echo -e "# Cmd Line : ${FORCE_CONFIG_LINEID}"
|
|
echo -e "# Command : ${COLOR_YELLOW}${REPLY}${COLOR_RESET}"
|
|
|
|
eval "${@}"
|
|
if [ $? -gt 0 ]
|
|
then
|
|
printf "\n# > FAILED <\n"
|
|
else
|
|
if [ "${1}" = "interface" -o "${1}" = "router" ]
|
|
then
|
|
echo >>"${FIREHOL_TEMP_CONFIG}"
|
|
else
|
|
printf " " >>"${FIREHOL_TEMP_CONFIG}"
|
|
fi
|
|
|
|
printf "%s\n" "${REPLY}" >>"${FIREHOL_TEMP_CONFIG}"
|
|
|
|
lineid=$[lineid + 1]
|
|
FORCE_CONFIG_LINEID="${lineid}@${FIREHOL_CONFIG}"
|
|
|
|
printf "\n# > OK <\n"
|
|
fi
|
|
break
|
|
;;
|
|
esac
|
|
|
|
break
|
|
done
|
|
done
|
|
|
|
exit 0
|
|
fi
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# MAIN PROCESSING - help wizard
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
|
|
if [ "${FIREHOL_MODE}" = "WIZARD" ]
|
|
then
|
|
# require commands for wizard mode
|
|
common_require_cmd $PROGRAM_FILE IP_CMD
|
|
common_require_cmd $PROGRAM_FILE SS_CMD
|
|
common_require_cmd $PROGRAM_FILE DATE_CMD
|
|
common_require_cmd $PROGRAM_FILE HOSTNAME_CMD
|
|
|
|
wizard_ask() {
|
|
local prompt="${1}" def="${2}" ans= c= t=
|
|
shift 2
|
|
|
|
echo >&2
|
|
|
|
while [ 1 = 1 ]
|
|
do
|
|
printf >&2 "%s [%s] > " "${prompt}" "${def}"
|
|
read
|
|
|
|
ans="${REPLY}"
|
|
|
|
test -z "${ans}" && ans="${def}"
|
|
|
|
c=0
|
|
while [ $c -le $# ]
|
|
do
|
|
eval t="\${${c}}"
|
|
|
|
test "${ans}" = "${t}" && break
|
|
c=$[c + 1]
|
|
done
|
|
|
|
test $c -le $# && return $c
|
|
|
|
printf >&2 "*** '${ans}' is not a valid answer. Pick one of "
|
|
printf >&2 "%s " "${@}"
|
|
echo >&2
|
|
echo >&2
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
ip_in_net() {
|
|
local ip="${1}" net="${2}"
|
|
shift 2
|
|
|
|
if [ -z "${ip}" -o -z "${net}" ]
|
|
then
|
|
return 1
|
|
fi
|
|
|
|
test "${net}" = "default" && net="0.0.0.0/0"
|
|
|
|
set -- `echo ${ip} | ${TR_CMD} './' ' '`
|
|
local i1=${1} i2=${2} i3=${3} i4=${4}
|
|
|
|
set -- `echo ${net} | ${TR_CMD} './' ' '`
|
|
local n1=${1} n2=${2} n3=${3} n4=${4} n5=${5:-32}
|
|
|
|
local i=$[i1*256*256*256 + i2*256*256 + i3*256 + i4]
|
|
local n=$[n1*256*256*256 + n2*256*256 + n3*256 + n4]
|
|
|
|
# echo "IP : '${i1}' . '${i2}' . '${i3}' . '${i4}'"
|
|
# echo "NET: '${n1}' . '${n2}' . '${n3}' . '${n4}' / '${n5}'"
|
|
|
|
local d=1 c=${n5}
|
|
while [ $c -lt 32 ]
|
|
do
|
|
c=$[c + 1]
|
|
d=$[d * 2]
|
|
done
|
|
|
|
local nm=$[n + d - 1]
|
|
|
|
printf "# INFO: Is ${ip} part of network ${net}? "
|
|
|
|
if [ ${i} -ge ${n} -a ${i} -le ${nm} ]
|
|
then
|
|
echo "yes"
|
|
return 0
|
|
else
|
|
echo "no"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
ip_is_net() {
|
|
local ip="${1}" net="${2}"
|
|
shift 2
|
|
|
|
if [ -z "${ip}" -o -z "${net}" ]
|
|
then
|
|
return 1
|
|
fi
|
|
|
|
test "${net}" = "default" && net="0.0.0.0/0"
|
|
|
|
set -- `echo ${ip} | ${TR_CMD} './' ' '`
|
|
local i1=${1} i2=${2} i3=${3} i4=${4} i5=${5:-32}
|
|
|
|
set -- `echo ${net} | ${TR_CMD} './' ' '`
|
|
local n1=${1} n2=${2} n3=${3} n4=${4} n5=${5:-32}
|
|
|
|
local i=$[i1*256*256*256 + i2*256*256 + i3*256 + i4]
|
|
local n=$[n1*256*256*256 + n2*256*256 + n3*256 + n4]
|
|
|
|
if [ ${i} -eq ${n} -a ${i5} -eq ${n5} ]
|
|
then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
ip2net() {
|
|
local ip="${1}"; shift
|
|
|
|
if [ -z "${ip}" ]
|
|
then
|
|
return 0
|
|
fi
|
|
|
|
if [ "${ip}" = "default" ]
|
|
then
|
|
echo "default"
|
|
return 0
|
|
fi
|
|
|
|
set -- `echo ${ip} | ${TR_CMD} './' ' '`
|
|
local i1=${1} i2=${2} i3=${3} i4=${4} i5=${5:-32}
|
|
|
|
if [ "${i5}" = "32" ]
|
|
then
|
|
echo ${i1}.${i2}.${i3}.${i4}
|
|
else
|
|
echo ${i1}.${i2}.${i3}.${i4}/${i5}
|
|
fi
|
|
}
|
|
|
|
ips2net() {
|
|
|
|
(
|
|
if [ "A${1}" = "A-" ]
|
|
then
|
|
while read ip
|
|
do
|
|
ip2net ${ip}
|
|
done
|
|
else
|
|
while [ ! -z "${1}" ]
|
|
do
|
|
ip2net ${1}
|
|
shift
|
|
done
|
|
fi
|
|
) | ${SORT_CMD} | ${UNIQ_CMD} | ${TR_CMD} "\n" " "
|
|
}
|
|
|
|
cd "${FIREHOL_DIR}"
|
|
${MKDIR_CMD} ports
|
|
${MKDIR_CMD} keys
|
|
cd ports
|
|
${MKDIR_CMD} tcp
|
|
${MKDIR_CMD} udp
|
|
|
|
emit_version >&2
|
|
|
|
${CAT_CMD} >&2 <<EOF
|
|
FireHOL will now try to figure out its configuration file on this system.
|
|
Please have all the services and network interfaces on this system running.
|
|
|
|
Your running firewall will not be stopped or altered.
|
|
|
|
You can re-run the same command with output redirection to get the config
|
|
to a file. Example:
|
|
|
|
EOF
|
|
echo >&2 "${PROGRAM_FILE} helpme >/tmp/firehol.conf"
|
|
echo >&2
|
|
echo >&2
|
|
|
|
echo >&2
|
|
echo >&2 "Building list of known services."
|
|
echo >&2 "Please wait..."
|
|
|
|
${CAT_CMD} /etc/services |\
|
|
${TR_CMD} '\t' ' ' |\
|
|
${SED_CMD} "s/ \+/ /g" >services
|
|
|
|
for c in `echo ${!server_*} | ${TR_CMD} ' ' '\n' | ${GREP_CMD} "_ports$"`
|
|
do
|
|
serv=`echo $c | ${SED_CMD} "s/server_//" | ${SED_CMD} "s/_ports//"`
|
|
|
|
eval "ret=\${$c}"
|
|
for x in ${ret}
|
|
do
|
|
proto=`echo $x | ${CUT_CMD} -d '/' -f 1`
|
|
port=`echo $x | ${CUT_CMD} -d '/' -f 2`
|
|
|
|
test ! -d "${proto}" && continue
|
|
|
|
nport=`${EGREP_CMD} "^${port}[[:space:]][0-9]+/${proto}" services | ${CUT_CMD} -d ' ' -f 2 | ${CUT_CMD} -d '/' -f 1`
|
|
test -z "${nport}" && nport="${port}"
|
|
|
|
echo "server ${serv}" >"${proto}/${nport}"
|
|
done
|
|
done
|
|
|
|
echo "server ftp" >tcp/21
|
|
echo "server nfs" >udp/2049
|
|
|
|
echo "client amanda" >udp/10080
|
|
|
|
echo "server dhcp" >udp/67
|
|
echo "server dhcp" >tcp/67
|
|
|
|
echo "client dhcp" >udp/68
|
|
echo "client dhcp" >tcp/68
|
|
|
|
echo "server emule" >tcp/4662
|
|
|
|
echo "server pptp" >tcp/1723
|
|
|
|
echo "server samba" >udp/137
|
|
echo "server samba" >udp/138
|
|
echo "server samba" >tcp/139
|
|
|
|
|
|
wizard_ask "Press RETURN to start." "continue" "continue"
|
|
|
|
echo >&2
|
|
echo >&2 "--- snip --- snip --- snip --- snip ---"
|
|
echo >&2
|
|
|
|
${CAT_CMD} <<EOF
|
|
#
|
|
# FireHOL configuration (autogenerated)
|
|
#
|
|
# This config will have the same effect as NO PROTECTION!
|
|
# Everything that found to be running, is allowed.
|
|
# YOU SHOULD NEVER USE THIS CONFIG AS-IS.
|
|
#
|
|
# Date: `${DATE_CMD}` on host `${HOSTNAME_CMD}`
|
|
#
|
|
# IMPORTANT:
|
|
# The TODOs below, are *YOUR* to-dos!
|
|
#
|
|
|
|
EOF
|
|
|
|
# globals for routing
|
|
set -a found_interfaces=
|
|
set -a found_ips=
|
|
set -a found_nets=
|
|
set -a found_excludes=
|
|
|
|
helpme_iface() {
|
|
local route="${1}"; shift
|
|
local i="${1}"; shift
|
|
local iface="${1}"; shift
|
|
local ifip="${1}"; shift
|
|
local ifnets="${1}"; shift
|
|
local ifreason="${1}"; shift
|
|
|
|
# one argument left: ifnets_excluded
|
|
|
|
if [ "${route}" = "route" ]
|
|
then
|
|
found_interfaces[$i]="${iface}"
|
|
found_ips[$i]="${ifip}"
|
|
found_nets[$i]="${ifnets}"
|
|
found_excludes[$i]="${1}"
|
|
fi
|
|
|
|
if [ "${ifnets}" = "default" ]
|
|
then
|
|
ifnets="not \"\${UNROUTABLE_IPS} ${1}\""
|
|
else
|
|
ifnets="\"${ifnets}\""
|
|
fi
|
|
|
|
# output the interface
|
|
echo
|
|
echo "# Interface No $i."
|
|
echo "# The purpose of this interface is to control the traffic"
|
|
if [ ! -z "${ifreason}" ]
|
|
then
|
|
echo "# ${ifreason}."
|
|
else
|
|
echo "# on the ${iface} interface with IP ${ifip} (net: ${ifnets})."
|
|
fi
|
|
|
|
echo "# TODO: Change \"if${i}\" to something with meaning to you."
|
|
echo "# TODO: Check the optional rule parameters (src/dst)."
|
|
echo "# Remove 'dst ${ifip}' if this is dynamically assigned."
|
|
echo "# To add IPv6, read http://firehol.org/upgrade/#config-version-6"
|
|
echo "interface4 ${iface} if${i} src ${ifnets} dst ${ifip}"
|
|
echo
|
|
echo " # The default policy is DROP. You can be more polite with REJECT."
|
|
echo " # Prefer to be polite on your own clients to prevent timeouts."
|
|
echo " policy drop"
|
|
echo
|
|
echo " # If you don't trust the clients behind ${iface} (net ${ifnets}),"
|
|
echo " # add something like this."
|
|
echo " # > protection strong"
|
|
echo
|
|
echo " # Here are the services listening on ${iface}."
|
|
echo " # TODO: Normally, you will have to remove those not needed."
|
|
|
|
(
|
|
local x=
|
|
local ports=
|
|
for x in `${SS_CMD} -tln | ${SED_CMD} "s|:::|\*:|g" | ${SED_CMD} "s|::ffff:||g" | ${EGREP_CMD} " (${ifip}|\*):[0-9]+" | ${CUT_CMD} -d ':' -f 2 | ${CUT_CMD} -d ' ' -f 1 | ${SORT_CMD} -n | ${UNIQ_CMD}`
|
|
do
|
|
if [ -f "tcp/${x}" ]
|
|
then
|
|
echo " `${CAT_CMD} tcp/${x}` accept"
|
|
else
|
|
ports="${ports} tcp/${x}"
|
|
fi
|
|
done
|
|
|
|
for x in `${SS_CMD} -uln | ${SED_CMD} "s|:::|\*:|g" | ${SED_CMD} "s|::ffff:||g" | ${EGREP_CMD} " (${ifip}|\*):[0-9]+" | ${CUT_CMD} -d ':' -f 2 | ${CUT_CMD} -d ' ' -f 1 | ${SORT_CMD} -n | ${UNIQ_CMD}`
|
|
do
|
|
if [ -f "udp/${x}" ]
|
|
then
|
|
echo " `${CAT_CMD} udp/${x}` accept"
|
|
else
|
|
ports="${ports} udp/${x}"
|
|
fi
|
|
done
|
|
|
|
echo " server ICMP accept"
|
|
|
|
echo "${ports}" | ${TR_CMD} " " "\n" | ${SORT_CMD} -n | ${UNIQ_CMD} | ${TR_CMD} "\n" " " >unknown.ports
|
|
) | ${SORT_CMD} | ${UNIQ_CMD}
|
|
|
|
echo
|
|
echo " # The following ${iface} services are not known by FireHOL:"
|
|
${CAT_CMD} unknown.ports | ${FOLD_CMD} -s -w 65 | ${SED_CMD} "s|^ *|\t# |"
|
|
echo
|
|
|
|
echo
|
|
echo " # Custom service definitions for the above unknown services."
|
|
local ts=
|
|
local tscount=0
|
|
for ts in `${CAT_CMD} unknown.ports`
|
|
do
|
|
local tscount=$[tscount + 1]
|
|
echo " server custom if${i}_${tscount} ${ts} any accept"
|
|
done
|
|
|
|
echo
|
|
echo " # The following means that this machine can REQUEST anything via ${iface}."
|
|
echo " # TODO: On production servers, avoid this and allow only the"
|
|
echo " # client services you really need."
|
|
echo " client all accept"
|
|
echo
|
|
}
|
|
|
|
interfaces=`${IP_CMD} link show | ${EGREP_CMD} "^[0-9A-Za-z]+:" | ${CUT_CMD} -d ':' -f 2 | ${SED_CMD} "s/^ //" | ${SED_CMD} "s/@[A-Za-z0-9]*//" | ${GREP_CMD} -v "^lo$" | ${SORT_CMD} | ${UNIQ_CMD} | ${TR_CMD} "\n" " "`
|
|
gw_if=`${IP_CMD} route show | ${GREP_CMD} "^default" | ${SED_CMD} "s/dev /dev:/g" | ${TR_CMD} " " "\n" | ${GREP_CMD} "^dev:" | ${CUT_CMD} -d ':' -f 2`
|
|
gw_ip=`${IP_CMD} route show | ${GREP_CMD} "^default" | ${SED_CMD} "s/via /via:/g" | ${TR_CMD} " " "\n" | ${GREP_CMD} "^via:" | ${CUT_CMD} -d ':' -f 2 | ips2net -`
|
|
|
|
i=0
|
|
for iface in ${interfaces}
|
|
do
|
|
echo "# INFO: Processing interface '${iface}'"
|
|
ips=`${IP_CMD} addr show dev ${iface} | ${SED_CMD} "s/ \+/ /g" | ${GREP_CMD} "^ inet " | ${CUT_CMD} -d ' ' -f 3 | ${CUT_CMD} -d '/' -f 1 | ips2net -`
|
|
peer=`${IP_CMD} addr show dev ${iface} | ${SED_CMD} "s/ \+/ /g" | ${SED_CMD} "s/peer /peer:/g" | ${TR_CMD} " " "\n" | ${GREP_CMD} "^peer:" | ${CUT_CMD} -d ':' -f 2 | ips2net -`
|
|
nets=`${IP_CMD} route show dev ${iface} | ${CUT_CMD} -d ' ' -f 1 | ips2net -`
|
|
|
|
if [ -z "${ips}" -o -z "${nets}" ]
|
|
then
|
|
echo
|
|
echo "# IMPORTANT: "
|
|
echo "# Ignoring interface '${iface}' because does not have an IP or route."
|
|
echo
|
|
continue
|
|
fi
|
|
|
|
for ip in ${ips}
|
|
do
|
|
echo "# INFO: Processing IP ${ip} of interface '${iface}'"
|
|
|
|
ifreason=""
|
|
|
|
# find all the networks this IP can access directly
|
|
# or through its peer
|
|
netcount=0
|
|
ifnets=
|
|
ofnets=
|
|
for net in ${nets}
|
|
do
|
|
test "${net}" = "default" && continue
|
|
|
|
found=1
|
|
ip_in_net ${ip} ${net}
|
|
found=$?
|
|
|
|
if [ ${found} -gt 0 -a ! -z "${peer}" ]
|
|
then
|
|
ip_in_net ${peer} ${net}
|
|
found=$?
|
|
fi
|
|
|
|
if [ ${found} -eq 0 ]
|
|
then
|
|
# Add it to ifnets
|
|
f=0; ff=0
|
|
while [ $f -lt $netcount ]
|
|
do
|
|
if ip_in_net ${net} ${ifnets[$f]}
|
|
then
|
|
# Already satisfied
|
|
ff=1
|
|
elif ip_in_net ${ifnets[$f]} ${net}
|
|
then
|
|
# New one is superset of old
|
|
ff=1
|
|
ifnets[$f]=${net}
|
|
fi
|
|
|
|
f=$[f + 1]
|
|
done
|
|
|
|
if [ $ff -eq 0 ]
|
|
then
|
|
# Add it
|
|
netcount=$[netcount + 1]
|
|
ifnets=(${net} "${ifnets[@]}")
|
|
fi
|
|
else
|
|
ofnets=(${net} "${ofnets[@]}")
|
|
fi
|
|
done
|
|
|
|
# find all the networks this IP can access through gateways
|
|
if [ ! -z "${ofnets[*]}" ]
|
|
then
|
|
for net in "${ofnets[@]}"
|
|
do
|
|
test "${net}" = "default" && continue
|
|
|
|
nn=`echo "${net}" | ${CUT_CMD} -d "/" -f 1`
|
|
gw=`${IP_CMD} route show ${nn} dev ${iface} | ${EGREP_CMD} "^${nn}[[:space:]]+via[[:space:]][0-9\.]+" | ${CUT_CMD} -d ' ' -f 3 | ips2net -`
|
|
test -z "${gw}" && continue
|
|
|
|
for nn in "${ifnets[@]}"
|
|
do
|
|
test "${nn}" = "default" && continue
|
|
|
|
if ip_in_net ${gw} ${nn}
|
|
then
|
|
echo "# INFO: Route ${net} is accessed through ${gw}"
|
|
|
|
# Add it to ifnets
|
|
f=0; ff=0
|
|
while [ $f -lt $netcount ]
|
|
do
|
|
if ip_in_net ${net} ${ifnets[$f]}
|
|
then
|
|
# Already satisfied
|
|
ff=1
|
|
elif ip_in_net ${ifnets[$f]} ${net}
|
|
then
|
|
# New one is superset of old
|
|
ff=1
|
|
ifnets[$f]=${net}
|
|
fi
|
|
|
|
f=$[f + 1]
|
|
done
|
|
|
|
if [ $ff -eq 0 ]
|
|
then
|
|
# Add it
|
|
netcount=$[netcount + 1]
|
|
ifnets=(${net} "${ifnets[@]}")
|
|
fi
|
|
break
|
|
fi
|
|
done
|
|
done
|
|
fi
|
|
|
|
# Don't produce an interface if this is just a peer that is also the default gw
|
|
def_ignore_ifnets=0
|
|
if (test ${netcount} -eq 1 -a "${gw_if}" = "${iface}" && ip_is_net "${peer}" "${ifnets[*]}" && ip_is_net "${gw_ip}" "${peer}")
|
|
then
|
|
echo "# INFO: Skipping ${iface} peer ${ifnets[*]} only interface (default gateway)."
|
|
echo
|
|
def_ignore_ifnets=1
|
|
else
|
|
i=$[i + 1]
|
|
helpme_iface route $i "${iface}" "${ip}" "${ifnets[*]}" "${ifreason}"
|
|
fi
|
|
|
|
# Is this interface the default gateway too?
|
|
if [ "${gw_if}" = "${iface}" ]
|
|
then
|
|
for nn in "${ifnets[@]}"
|
|
do
|
|
if ip_in_net "${gw_ip}" ${nn}
|
|
then
|
|
echo "# INFO: Default gateway ${gw_ip} is part of network ${nn}"
|
|
|
|
i=$[i + 1]
|
|
helpme_iface route $i "${iface}" "${ip}" "default" "from/to unknown networks behind the default gateway ${gw_ip}" "`test ${def_ignore_ifnets} -eq 0 && echo "${ifnets[*]}"`"
|
|
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
done
|
|
done
|
|
|
|
echo
|
|
echo "# The above $i interfaces were found active at this moment."
|
|
echo "# Add more interfaces that can potentially be activated in the future."
|
|
echo "# FireHOL will not complain if you setup a firewall on an interface that is"
|
|
echo "# not active when you activate the firewall."
|
|
echo "# If you don't setup an interface, FireHOL will drop all traffic from or to"
|
|
echo "# this interface, if and when it becomes available."
|
|
echo "# Also, if an interface name dynamically changes (i.e. ppp0 may become ppp1)"
|
|
echo "# you can use the plus (+) character to match all of them (i.e. ppp+)."
|
|
echo
|
|
|
|
if [ "1" = "`${CAT_CMD} /proc/sys/net/ipv4/ip_forward`" ]
|
|
then
|
|
x=0
|
|
i=0
|
|
while [ $i -lt ${#found_interfaces[*]} ]
|
|
do
|
|
i=$[i + 1]
|
|
|
|
inface="${found_interfaces[$i]}"
|
|
src="${found_nets[$i]}"
|
|
|
|
case "${src}" in
|
|
"default")
|
|
src="not \"\${UNROUTABLE_IPS} ${found_excludes[$i]}\""
|
|
;;
|
|
|
|
*)
|
|
src="\"${src}\""
|
|
;;
|
|
esac
|
|
|
|
j=0
|
|
while [ $j -lt ${#found_interfaces[*]} ]
|
|
do
|
|
j=$[j + 1]
|
|
|
|
test $j -eq $i && continue
|
|
|
|
outface="${found_interfaces[$j]}"
|
|
dst="${found_nets[$j]}"
|
|
dst_ip="${found_ips[$j]}"
|
|
|
|
case "${dst}" in
|
|
"default")
|
|
dst="not \"\${UNROUTABLE_IPS} ${found_excludes[$j]}\""
|
|
;;
|
|
|
|
*)
|
|
dst="\"${dst}\""
|
|
;;
|
|
esac
|
|
|
|
# Make sure we are not routing to the same subnet
|
|
test "${inface}" = "${outface}" -a "${src}" = "${dst}" && continue
|
|
|
|
# Make sure this is not a duplicate router
|
|
key="`echo ${inface}/${src}-${outface}/${dst} | ${TR_CMD} "/ \\\$\\\"{}" "______"`"
|
|
test -f "${FIREHOL_DIR}/keys/${key}" && continue
|
|
${TOUCH_CMD} "${FIREHOL_DIR}/keys/${key}"
|
|
|
|
x=$[x + 1]
|
|
if [ $x -lt 10 ]
|
|
then
|
|
lx="0$x"
|
|
else
|
|
lx="$x"
|
|
fi
|
|
|
|
echo
|
|
echo "# Router No ${x}."
|
|
echo "# Clients on ${inface} (from ${src}) accessing servers on ${outface} (to ${dst})."
|
|
echo "# TODO: Change \"router${lx}\" to something with meaning to you."
|
|
echo "# TODO: Check the optional rule parameters (src/dst)."
|
|
echo "# To add IPv6, read http://firehol.org/upgrade/#config-version-6"
|
|
echo "router4 router${lx} inface ${inface} outface ${outface} src ${src} dst ${dst}"
|
|
echo
|
|
echo " # If you don't trust the clients on ${inface} (from ${src}), or"
|
|
echo " # if you want to protect the servers on ${outface} (to ${dst}),"
|
|
echo " # uncomment the following line."
|
|
echo " # > protection strong"
|
|
echo
|
|
echo " # To NAT client requests on the output of ${outface}, add this."
|
|
echo " # > masquerade"
|
|
|
|
echo " # Alternatively, you can SNAT them by placing this at the top of this config:"
|
|
echo " # > snat to ${dst_ip} outface ${outface} src ${src} dst ${dst}"
|
|
echo " # SNAT commands can be enhanced using 'proto', 'sport', 'dport', etc in order to"
|
|
echo " # NAT only some specific traffic."
|
|
echo
|
|
echo " # TODO: This will allow all traffic to pass."
|
|
echo " # If you remove it, no REQUEST will pass matching this traffic."
|
|
echo " route all accept"
|
|
echo
|
|
done
|
|
done
|
|
|
|
if [ ${x} -eq 0 ]
|
|
then
|
|
echo
|
|
echo
|
|
echo "# No router statements have been produced, because your server"
|
|
echo "# does not seem to need any."
|
|
echo
|
|
fi
|
|
else
|
|
echo
|
|
echo
|
|
echo "# No router statements have been produced, because your server"
|
|
echo "# is not configured for forwarding traffic."
|
|
echo
|
|
fi
|
|
|
|
exit 0
|
|
fi
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
#
|
|
# MAIN PROCESSING
|
|
#
|
|
# ------------------------------------------------------------------------------
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# make sure we are alone
|
|
firehol_concurrent_run_lock
|
|
|
|
|
|
# --- Initialization -----------------------------------------------------------
|
|
|
|
# let the config_line know our main configuration file
|
|
PROGRAM_CONFIG="${FIREHOL_CONFIG}"
|
|
|
|
progress "Saving active firewall to a temporary file"
|
|
if [ $ENABLE_IPV4 -eq 1 ]
|
|
then
|
|
fixed_save ${IPTABLES_SAVE_CMD} >${FIREHOL_SAVED}.new
|
|
status4=$?
|
|
else
|
|
status4=0
|
|
fi
|
|
|
|
if [ $ENABLE_IPV6 -eq 1 ]
|
|
then
|
|
fixed_save ${IP6TABLES_SAVE_CMD} >${FIREHOL_SAVED6}.new
|
|
status6=$?
|
|
else
|
|
status6=0
|
|
fi
|
|
|
|
if [ $status4 -eq 0 -a $status6 -eq 0 ]
|
|
then
|
|
test -f ${FIREHOL_SAVED}.new && $MV_CMD ${FIREHOL_SAVED}.new ${FIREHOL_SAVED}
|
|
test -f ${FIREHOL_SAVED6}.new && $MV_CMD ${FIREHOL_SAVED6}.new ${FIREHOL_SAVED6}
|
|
|
|
success # "Saving active firewall to a temporary file"
|
|
else
|
|
${RM_CMD} -f "${FIREHOL_SAVED}" "${FIREHOL_SAVED6}"
|
|
failure # "Saving active firewall to a temporary file"
|
|
exit 1
|
|
fi
|
|
|
|
declare -a FIREHOL_ARGS=("${FIREHOL_CONFIG}" "${FIREHOL_CONFIG_DIR}" "${FIREHOL_SERVICES_DIR}" "${@}")
|
|
if [ "${FIREHOL_MODE}" = "START" -a ${FIREHOL_RESTORE_INSTEAD_OF_START} -eq 1 ]
|
|
then
|
|
firehol_restore_last_activated_firewall
|
|
if [ $? -eq 0 ]
|
|
then
|
|
${RM_CMD} -f "${FIREHOL_SAVED}" "${FIREHOL_SAVED6}" >/dev/null 2>&1
|
|
FIREHOL_ACTIVATED_SUCCESSFULLY=1
|
|
exit 0
|
|
fi
|
|
# warning "Starting the firewall normally..."
|
|
fi
|
|
|
|
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
|
|
# clear all chains
|
|
firehol_filter_chains=
|
|
firehol_filter6_chains=
|
|
prepare_firewall_for_activation() {
|
|
load_kernel_module ip_tables
|
|
load_kernel_module nf_conntrack
|
|
if [ $ENABLE_IPV6 -eq 1 ]
|
|
then
|
|
load_kernel_module ip6_tables
|
|
fi
|
|
|
|
for m in ${!FIREHOL_KERNEL_MODULES[*]}
|
|
do
|
|
postprocess -ne -ns load_kernel_module $m
|
|
done
|
|
|
|
# Find all tables supported
|
|
local t= tables= tables6= chains= c= policy=
|
|
if [ $ENABLE_IPV4 -eq 1 ]; then
|
|
tables=`${CAT_CMD} /proc/net/ip_tables_names`
|
|
for t in ${tables}
|
|
do
|
|
# Reset/empty this table.
|
|
${IPTABLES_CMD} -t "${t}" -F || exit 1
|
|
${IPTABLES_CMD} -t "${t}" -X || exit 1
|
|
${IPTABLES_CMD} -t "${t}" -Z || exit 1
|
|
|
|
# Find all default chains in this table.
|
|
chains=`${IPTABLES_CMD} -t "${t}" -nL | ${GREP_CMD} "^Chain " | ${CUT_CMD} -d ' ' -f 2`
|
|
|
|
# If this is the 'filter' table, remember the default chains.
|
|
# This will be used at the end to make it DROP all packets.
|
|
test "${t}" = "filter" && firehol_filter_chains="${chains}"
|
|
|
|
# Set the policy to ACCEPT on all default chains.
|
|
for c in ${chains}
|
|
do
|
|
policy=ACCEPT
|
|
if [ "${t}" = "filter" ]
|
|
then
|
|
eval "policy=\${FIREHOL_${c}_ACTIVATION_POLICY}"
|
|
fi
|
|
${IPTABLES_CMD} -t "${t}" -P "${c}" $policy || exit 1
|
|
done
|
|
done
|
|
|
|
# Allow existing traffic to continue:
|
|
# insert as the first rule in each chain, making it easy to
|
|
# undo once the firewall is completely established
|
|
if [ ${FIREHOL_FAST_ACTIVATION} -ne 1 -a \
|
|
"${FIREHOL_ESTABLISHED_ACTIVATION_ACCEPT}" = "1" ]
|
|
then
|
|
${IPTABLES_CMD} -I INPUT 1 \
|
|
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
${IPTABLES_CMD} -I OUTPUT 1 \
|
|
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
${IPTABLES_CMD} -I FORWARD 1 \
|
|
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
fi
|
|
fi
|
|
|
|
if [ $ENABLE_IPV6 -eq 1 ]; then
|
|
tables6=`${CAT_CMD} /proc/net/ip6_tables_names`
|
|
for t in ${tables6}
|
|
do
|
|
# Reset/empty this table.
|
|
${IP6TABLES_CMD} -t "${t}" -F || exit 1
|
|
${IP6TABLES_CMD} -t "${t}" -X || exit 1
|
|
${IP6TABLES_CMD} -t "${t}" -Z || exit 1
|
|
|
|
# Find all default chains in this table.
|
|
chains=`${IP6TABLES_CMD} -t "${t}" -nL | ${GREP_CMD} "^Chain " | ${CUT_CMD} -d ' ' -f 2`
|
|
|
|
# If this is the 'filter' table, remember the default chains.
|
|
# This will be used at the end to make it DROP all packets.
|
|
test "${t}" = "filter" && firehol_filter6_chains="${chains}"
|
|
|
|
# Set the policy to ACCEPT on all default chains.
|
|
for c in ${chains}
|
|
do
|
|
policy=ACCEPT
|
|
if [ "${t}" = "filter" ]
|
|
then
|
|
eval "policy=\${FIREHOL_${c}_ACTIVATION_POLICY}"
|
|
fi
|
|
${IP6TABLES_CMD} -t "${t}" -P "${c}" $policy || exit 1
|
|
done
|
|
done
|
|
|
|
# Allow existing traffic to continue
|
|
if [ ${FIREHOL_FAST_ACTIVATION} -ne 1 -a \
|
|
"${FIREHOL_ESTABLISHED_ACTIVATION_ACCEPT}" = "1" ]
|
|
then
|
|
${IP6TABLES_CMD} -I INPUT 1 \
|
|
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
${IP6TABLES_CMD} -I OUTPUT 1 \
|
|
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
${IP6TABLES_CMD} -I FORWARD 1 \
|
|
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# drop everything
|
|
finalize_activated_firewall() {
|
|
# Make it drop everything on table 'filter'.
|
|
local c=
|
|
if [ $ENABLE_IPV4 -eq 1 ]; then
|
|
for c in ${firehol_filter_chains}
|
|
do
|
|
${IPTABLES_CMD} -t filter -P "${c}" DROP || exit 1
|
|
done
|
|
|
|
# Remove rules inserted which were to keep existing traffic
|
|
# alive during activation
|
|
if [ ${FIREHOL_FAST_ACTIVATION} -ne 1 -a \
|
|
"${FIREHOL_ESTABLISHED_ACTIVATION_ACCEPT}" = "1" ]
|
|
then
|
|
${IPTABLES_CMD} -D INPUT \
|
|
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
${IPTABLES_CMD} -D OUTPUT \
|
|
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
${IPTABLES_CMD} -D FORWARD \
|
|
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
fi
|
|
fi
|
|
|
|
if [ $ENABLE_IPV6 -eq 1 ]; then
|
|
for c in ${firehol_filter6_chains}
|
|
do
|
|
${IP6TABLES_CMD} -t filter -P "${c}" DROP || exit 1
|
|
done
|
|
|
|
if [ ${FIREHOL_FAST_ACTIVATION} -ne 1 -a \
|
|
"${FIREHOL_ESTABLISHED_ACTIVATION_ACCEPT}" = "1" ]
|
|
then
|
|
${IP6TABLES_CMD} -D INPUT \
|
|
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
${IP6TABLES_CMD} -D OUTPUT \
|
|
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
${IP6TABLES_CMD} -D FORWARD \
|
|
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# this will be run when the first iptables command get executed in pre-process mode.
|
|
# so that its commands are prepended to the other iptables commands of the firewall
|
|
|
|
firewall_filtering_policy_common() {
|
|
local iptables_cmd="${1}"
|
|
|
|
set_work_function "Applying ${iptables_cmd} firewall activation policy (options: FIREHOL_INPUT_ACTIVATION_POLICY FIREHOL_OUTPUT_ACTIVATION_POLICY FIREHOL_FORWARD_ACTIVATION_POLICY)"
|
|
|
|
${iptables_cmd} -t filter -P INPUT "${FIREHOL_INPUT_ACTIVATION_POLICY}"
|
|
${iptables_cmd} -t filter -P OUTPUT "${FIREHOL_OUTPUT_ACTIVATION_POLICY}"
|
|
${iptables_cmd} -t filter -P FORWARD "${FIREHOL_FORWARD_ACTIVATION_POLICY}"
|
|
|
|
# Accept everything in/out the loopback device.
|
|
if [ "${FIREHOL_TRUST_LOOPBACK}" = "1" -o "${FIREHOL_TRUST_LOOPBACK}" = "loose" ]
|
|
then
|
|
${iptables_cmd} -t filter -A INPUT -i lo -j ACCEPT
|
|
${iptables_cmd} -t filter -A OUTPUT -o lo -j ACCEPT
|
|
|
|
elif [ "${FIREHOL_TRUST_LOOPBACK}" = "strict" ]
|
|
then
|
|
set_work_function "Trusting ${iptables_cmd} lo (option: FIREHOL_TRUST_LOOPBACK)"
|
|
if running_ipv4
|
|
then
|
|
${iptables_cmd} -t filter -A INPUT -i lo -s 127.0.0.0/8 -d 127.0.0.0/8 -j ACCEPT
|
|
${iptables_cmd} -t filter -A OUTPUT -o lo -s 127.0.0.0/8 -d 127.0.0.0/8 -j ACCEPT
|
|
fi
|
|
if running_ipv6
|
|
then
|
|
${iptables_cmd} -t filter -A INPUT -i lo -s ::1 -d ::1 -j ACCEPT
|
|
${iptables_cmd} -t filter -A OUTPUT -o lo -s ::1 -d ::1 -j ACCEPT
|
|
fi
|
|
fi
|
|
|
|
if [ "${FIREHOL_RULESET_MODE}" = "optimal" ]
|
|
then
|
|
set_work_function "Accepting all ESTABLISHED connections at the beginning of the firewall"
|
|
|
|
${iptables_cmd} -t filter -A INPUT -m conntrack --ctstate ESTABLISHED -j ACCEPT
|
|
${iptables_cmd} -t filter -A OUTPUT -m conntrack --ctstate ESTABLISHED -j ACCEPT
|
|
${iptables_cmd} -t filter -A FORWARD -m conntrack --ctstate ESTABLISHED -j ACCEPT
|
|
fi
|
|
if [ ! -z ${FIREHOL_GLOBAL_RPFILTER} ]
|
|
then
|
|
${iptables_cmd} -t raw -A PREROUTING -m rpfilter ${FIREHOL_GLOBAL_RPFILTER} -j DROP
|
|
fi
|
|
}
|
|
|
|
|
|
# this will be run when the first iptables command get executed in pre-process mode.
|
|
# so that its commands are prepended to the other iptables commands of the firewall
|
|
|
|
firewall_filtering_policy_common_late() {
|
|
if [ ${FIREHOL_FILTERING_STARTED} -eq 0 ]
|
|
then
|
|
return
|
|
fi
|
|
local iptables_cmd="${1}"
|
|
local main_chain="${2}"
|
|
local iptables_chain="${3}"
|
|
|
|
local oldns="${FIREHOL_NS_CURR}"
|
|
|
|
if [ "${iptables_cmd}" == "iptables" ]
|
|
then
|
|
FIREHOL_NS_CURR="ipv4"
|
|
else
|
|
FIREHOL_NS_CURR="ipv6"
|
|
fi
|
|
|
|
# RELATED should not be needed
|
|
# set_work_function "Accepting all RELATED connections at the beginning of the firewall"
|
|
#
|
|
# ${iptables_cmd} -t filter -A INPUT -m conntrack --ctstate RELATED -j ACCEPT
|
|
# ${iptables_cmd} -t filter -A OUTPUT -m conntrack --ctstate RELATED -j ACCEPT
|
|
# ${iptables_cmd} -t filter -A FORWARD -m conntrack --ctstate RELATED -j ACCEPT
|
|
|
|
if [ "${FIREHOL_ACCEPT_OUTPUT_UNMATCHED_TCP_RST}" = "1" -a "${main_chain}" = "OUTPUT" ]
|
|
then
|
|
set_work_function "Accepting TCP RST packets on OUTPUT (option: FIREHOL_ACCEPT_OUTPUT_UNMATCHED_TCP_RST)"
|
|
${iptables_cmd} -t filter -A ${iptables_chain} -p tcp --tcp-flags RST RST -j ACCEPT
|
|
fi
|
|
|
|
if [ "${FIREHOL_DROP_ORPHAN_TCP_ACK_FIN}" = "1" ]
|
|
then
|
|
set_work_function "Silently dropping TCP ACK+FIN packets (option: FIREHOL_DROP_ORPHAN_TCP_ACK_FIN)"
|
|
|
|
# Silently drop orphan TCP/ACK FIN packets
|
|
# before dropping INVALID below, otherwise these will be logged as INVALID too
|
|
${iptables_cmd} -t filter -A ${iptables_chain} -p tcp --tcp-flags ACK,FIN ACK,FIN -m conntrack --ctstate NEW,INVALID -j DROP
|
|
fi
|
|
|
|
if [ "${FIREHOL_DROP_ORPHAN_TCP_ACK_RST}" = "1" ]
|
|
then
|
|
set_work_function "Silently dropping TCP ACK+RST packets (option: FIREHOL_DROP_ORPHAN_TCP_ACK_RST)"
|
|
|
|
# Silently drop orphan TCP/ACK RST packets
|
|
# before dropping INVALID below, otherwise these will be logged as INVALID too
|
|
${iptables_cmd} -t filter -A ${iptables_chain} -p tcp --tcp-flags ACK,RST ACK,RST -m conntrack --ctstate NEW,INVALID -j DROP
|
|
|
|
fi
|
|
|
|
if [ "${FIREHOL_DROP_ORPHAN_TCP_ACK}" = "1" ]
|
|
then
|
|
set_work_function "Silently dropping TCP ACK packets (option: FIREHOL_DROP_ORPHAN_TCP_ACK)"
|
|
|
|
# Silently drop orphan TCP/ACK packets
|
|
# before dropping INVALID below, otherwise these will be logged as INVALID too
|
|
${iptables_cmd} -t filter -A ${iptables_chain} -p tcp --tcp-flags ACK ACK -m conntrack --ctstate NEW,INVALID -j DROP
|
|
fi
|
|
|
|
if [ "${FIREHOL_DROP_ORPHAN_TCP_RST}" = "1" ]
|
|
then
|
|
set_work_function "Silently dropping TCP RST packets (option: FIREHOL_DROP_ORPHAN_TCP_RST)"
|
|
|
|
# Silently drop orphan TCP/RST packets
|
|
# before dropping INVALID below, otherwise these will be logged as INVALID too
|
|
${iptables_cmd} -t filter -A ${iptables_chain} -p tcp --tcp-flags RST RST -m conntrack --ctstate NEW,INVALID -j DROP
|
|
fi
|
|
|
|
if [ "${iptables_cmd}" = "iptables" -a "${FIREHOL_DROP_ORPHAN_IPV4_ICMP_TYPE3}" = "1" ]
|
|
then
|
|
set_work_function "Silently dropping ICMP destination-unreachable packets (option: FIREHOL_DROP_ORPHAN_IPV4_ICMP_TYPE3)"
|
|
|
|
# Silently drop orphan ICMP/TYPE3 packets
|
|
# before dropping INVALID below, otherwise these will be logged as INVALID too
|
|
${iptables_cmd} -t filter -A ${iptables_chain} -p icmp --icmp-type destination-unreachable -m conntrack --ctstate NEW,INVALID -j DROP
|
|
fi
|
|
|
|
# Drop all invalid packets.
|
|
# Netfilter HOWTO suggests to DROP all INVALID packets.
|
|
if [ "${FIREHOL_DROP_INVALID}" = "1" -o "${FIREHOL_RULESET_MODE}" = "optimal" ]
|
|
then
|
|
set_work_function "Dropping ${iptables_cmd} connection tracker INVALID packets (option: FIREHOL_DROP_INVALID)"
|
|
|
|
if [ ${FIREHOL_LOG_DROP_INVALID} -eq 1 ]
|
|
then
|
|
rule table filter chain ${iptables_chain} state INVALID action ${FIREHOL_DROP_INVALID_ACTION} loglimit "${FIREHOL_DROP_INVALID_ACTION} INVALID ${iptables_chain}"
|
|
else
|
|
rule table filter chain ${iptables_chain} state INVALID action ${FIREHOL_DROP_INVALID_ACTION}
|
|
fi
|
|
fi
|
|
|
|
FIREHOL_NS_CURR="${oldns}"
|
|
}
|
|
|
|
firewall_filtering_policy() {
|
|
local oldns="${FIREHOL_NS_CURR}"
|
|
|
|
if [ ${ENABLE_IPV4} -eq 1 ]
|
|
then
|
|
FIREHOL_NS_CURR="ipv4"
|
|
firewall_filtering_policy_common iptables
|
|
firewall_filtering_policy_common_late iptables FORWARD FORWARD
|
|
fi
|
|
|
|
if [ ${ENABLE_IPV6} -eq 1 ]
|
|
then
|
|
FIREHOL_NS_CURR="ipv6"
|
|
firewall_filtering_policy_common ip6tables
|
|
firewall_filtering_policy_common_late ip6tables FORWARD FORWARD
|
|
fi
|
|
|
|
FIREHOL_NS_CURR="${oldns}"
|
|
}
|
|
|
|
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
|
|
progress "Processing file '${FIREHOL_CONFIG}'"
|
|
ret=0
|
|
|
|
# check if the user has given any iptables commands directly.
|
|
i="${IPTABLES_CMD}"
|
|
if [ ! -z "${IP6TABLES_CMD}" ]
|
|
then
|
|
if [ -z "${i}" ]
|
|
then
|
|
i="${IP6TABLES_CMD}"
|
|
else
|
|
i="(${i}|${IP6TABLES_CMD})"
|
|
fi
|
|
fi
|
|
if [ ! -z "`${CAT_CMD} ${FIREHOL_CONFIG} | ${EGREP_CMD} "${i}"`" ]
|
|
then
|
|
echo >&2
|
|
echo >&2
|
|
echo >&2 "ERROR:"
|
|
echo >&2 "${FIREHOL_CONFIG} contains ${IPTABLES_CMD} or ${IP6TABLES_CMD} statements."
|
|
echo >&2
|
|
echo >&2 "Replace these statements iptables or ip6tables respectively,"
|
|
echo >&2 "without a path, so that FireHOL can execute these commands at"
|
|
echo >&2 "firewall activation."
|
|
echo >&2
|
|
echo >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Run the configuration file.
|
|
|
|
if [ -n "$WAIT_FOR_IFACE" ]
|
|
then
|
|
common_require_cmd $PROGRAM_FILE DATE_CMD
|
|
common_require_cmd $PROGRAM_FILE IP_CMD
|
|
for i in "$WAIT_FOR_IFACE"
|
|
do
|
|
wait_for_interface $i
|
|
done
|
|
fi
|
|
|
|
enable -n trap # Disable the trap buildin shell command.
|
|
enable -n exit # Disable the exit buildin shell command.
|
|
|
|
FORCE_CONFIG_LINEID=
|
|
{ source ${FIREHOL_CONFIG} "${@}"; } # Run the configuration as a normal script.
|
|
source_status=$?
|
|
[ $source_status -ne 0 ] && ret=$[ret + 1]
|
|
[ $source_status -ne 0 ] && FIREHOL_CLEAN_TMP=0
|
|
FORCE_CONFIG_LINEID="FIN"
|
|
LAST_CONFIG_LINE="${FORCE_CONFIG_LINEID}"
|
|
|
|
close_cmd || ret=$[ret + 1]
|
|
close_master || ret=$[ret + 1]
|
|
|
|
enable trap # Enable the trap buildin shell command.
|
|
enable exit # Enable the exit buildin shell command.
|
|
|
|
if [ ${work_error} -gt 0 -o $ret -gt 0 ]
|
|
then
|
|
failure # "Processing file '${FIREHOL_CONFIG}'"
|
|
echo >&2
|
|
echo >&2
|
|
echo >&2 "NOTICE: No changes made to your firewall."
|
|
exit 1
|
|
fi
|
|
|
|
test ${FIREHOL_ENABLE_SPINNER} -eq 1 && spinner_end
|
|
success "${FIREHOL_COMMAND_COUNTER} iptables rules" # "Processing file '${FIREHOL_CONFIG}'"
|
|
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
|
|
if [ ${FIREHOL_FAST_ACTIVATION} -eq 1 ]
|
|
then
|
|
if [ ${FIREHOL_TRY} -eq 1 -a "$[FIREHOL_WAIT_USER_BEFORE_TRY]" -gt 60 ]
|
|
then
|
|
syslog info "Waiting user to try the new firewall."
|
|
echo >&2
|
|
echo >&2 "Your firewall is ready to be fast-activated..."
|
|
echo >&2 "If you don't continue, no changes will have been made to your firewall."
|
|
read >&2 -p "Activate the firewall? (just press enter to confirm or Control-C to stop) : " -t ${FIREHOL_WAIT_USER_BEFORE_TRY} -e || exit 1
|
|
echo >&2
|
|
fi
|
|
|
|
# construct the iptables-restore file from the split ones.
|
|
for n in table table6
|
|
do
|
|
case "${n}" in
|
|
table) out="${FIREHOL_OUTPUT}.fast" ;;
|
|
table6) out="${FIREHOL_OUTPUT}.fast6" ;;
|
|
esac
|
|
|
|
cd $FIREHOL_DIR/fast/${n}s || exit 1
|
|
for firehol_table in `$LS_CMD`
|
|
do
|
|
case "${firehol_table}" in
|
|
raw) chains="PREROUTING OUTPUT" ;;
|
|
mangle) chains="PREROUTING INPUT FORWARD OUTPUT POSTROUTING" ;;
|
|
nat) chains="PREROUTING INPUT OUTPUT POSTROUTING" ;;
|
|
filter) chains="INPUT FORWARD OUTPUT" ;;
|
|
security) chains="INPUT FORWARD OUTPUT" ;;
|
|
*) chains= ;;
|
|
esac
|
|
|
|
echo "*${firehol_table}"
|
|
test -f $FIREHOL_DIR/fast/${n}.${firehol_table}.policy && ${CAT_CMD} $FIREHOL_DIR/fast/${n}.${firehol_table}.policy
|
|
test -f $FIREHOL_DIR/fast/${n}.${firehol_table}.chains && ${CAT_CMD} $FIREHOL_DIR/fast/${n}.${firehol_table}.chains
|
|
test -f $FIREHOL_DIR/fast/${n}.${firehol_table}.rules && ${CAT_CMD} $FIREHOL_DIR/fast/${n}.${firehol_table}.rules
|
|
|
|
if [ "${FIREHOL_FAST_ACTIVATION_CHAINS_TRACE}" = "1" ]
|
|
then
|
|
for c in ${chains}
|
|
do
|
|
#for s in NONE EXPECTED SEEN_REPLY ASSURED CONFIRMED
|
|
#do
|
|
# prepare_iptables_log_arg "${firehol_table}.${c}.status=${s}"
|
|
# echo "-I ${c} -m conntrack --ctstatus ${s} -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_IPTABLES_ARG[@]}"
|
|
#done
|
|
for s in INVALID UNTRACKED NEW ESTABLISHED RELATED SNAT DNAT
|
|
do
|
|
prepare_iptables_log_arg "${firehol_table}.${c}.${s}"
|
|
echo "-I ${c} -m conntrack --ctstate ${s} -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_IPTABLES_ARG[@]}"
|
|
done
|
|
done
|
|
fi
|
|
|
|
echo "COMMIT"
|
|
done >>"${out}"
|
|
done
|
|
|
|
if [ "${FIREHOL_MODE}" = "DEBUG" ]
|
|
then
|
|
if [ $ENABLE_IPV4 -eq 1 ]
|
|
then
|
|
echo
|
|
echo "# IPv4 Rules:"
|
|
${CAT_CMD} ${FIREHOL_OUTPUT}.fast
|
|
fi
|
|
|
|
if [ $ENABLE_IPV6 -eq 1 ]
|
|
then
|
|
echo
|
|
echo "# IPv6 Rules:"
|
|
${CAT_CMD} ${FIREHOL_OUTPUT}.fast6
|
|
fi
|
|
|
|
echo
|
|
echo "# Commands to execute:"
|
|
${CAT_CMD} ${FIREHOL_OUTPUT} | ${SED_CMD} -e "s|#.*$||g" | ${EGREP_CMD} -v "^ *$"
|
|
exit 1
|
|
fi
|
|
|
|
# apply the new ipsets
|
|
if [ ${ENABLE_IPSET} -eq 1 ]
|
|
then
|
|
ipsets_apply_all || exit 1
|
|
fi
|
|
|
|
progress "Fast activating new firewall"
|
|
|
|
prepare_firewall_for_activation
|
|
|
|
# execute any postprocessing commands
|
|
# in FAST_ACTIVATION the output file does not have any iptables commands
|
|
# it might have kernel modules management, activation of routing, etc.
|
|
file close 21
|
|
source "${FIREHOL_OUTPUT}" "${@}"
|
|
if [ $? -ne 0 ]
|
|
then
|
|
work_runtime_error=$[work_runtime_error+1]
|
|
else
|
|
# attempt to restore this firewall from the generated commands
|
|
if [ $ENABLE_IPV4 -eq 1 ]
|
|
then
|
|
${IPTABLES_RESTORE_CMD} <${FIREHOL_OUTPUT}.fast >${FIREHOL_OUTPUT}.log 2>&1
|
|
status4=$?
|
|
else
|
|
status4=0
|
|
fi
|
|
|
|
if [ $status4 -ne 0 ]
|
|
then
|
|
# it failed
|
|
runtime_error error "CANNOT APPLY IN FAST MODE" FIN ${IPTABLES_RESTORE_CMD} "<${FIREHOL_OUTPUT}.fast"
|
|
|
|
work_runtime_error=$[work_runtime_error+1]
|
|
|
|
# find the line
|
|
echo >&2 "Offending line:"
|
|
line=`$CAT_CMD "${FIREHOL_OUTPUT}.log" | $GREP_CMD "Error occurred at line: " | $CUT_CMD -d ':' -f 2`
|
|
test -z "$line" && line=`$CAT_CMD "${FIREHOL_OUTPUT}.log" | $GREP_CMD "iptables-restore: line " | $GREP_CMD failed | $CUT_CMD -d ' ' -f 3`
|
|
${CAT_CMD} "${FIREHOL_OUTPUT}.fast" | ${HEAD_CMD} -n $line | ${TAIL_CMD} -n 1 >&2
|
|
echo >&2
|
|
|
|
# the rest of the script will restore the original firewall
|
|
else
|
|
if [ $ENABLE_IPV6 -eq 1 ]
|
|
then
|
|
${IP6TABLES_RESTORE_CMD} <${FIREHOL_OUTPUT}.fast6 >>${FIREHOL_OUTPUT}.log 2>&1
|
|
status6=$?
|
|
else
|
|
status6=0
|
|
fi
|
|
if [ $status6 -ne 0 ]
|
|
then
|
|
# it failed
|
|
runtime_error error "CANNOT APPLY IN FAST MODE" FIN ${IP6TABLES_RESTORE_CMD} "<${FIREHOL_OUTPUT}.fast6"
|
|
|
|
work_runtime_error=$[work_runtime_error+1]
|
|
|
|
# find the line
|
|
echo >&2 "Offending line:"
|
|
line=`$CAT_CMD "${FIREHOL_OUTPUT}.log" | $GREP_CMD "Error occurred at line: " | $CUT_CMD -d ':' -f 2`
|
|
test -z "$line" && line=`$CAT_CMD "${FIREHOL_OUTPUT}.log" | $GREP_CMD "ip6tables-restore: line " | $GREP_CMD failed | $CUT_CMD -d ' ' -f 3`
|
|
${CAT_CMD} "${FIREHOL_OUTPUT}.fast6" | ${HEAD_CMD} -n $line | ${TAIL_CMD} -n 1 >&2
|
|
echo >&2
|
|
|
|
# the rest of the script will restore the original firewall
|
|
else
|
|
finalize_activated_firewall
|
|
fi
|
|
fi
|
|
fi
|
|
else
|
|
|
|
if [ "${FIREHOL_MODE}" = "DEBUG" ]
|
|
then
|
|
file close 21
|
|
${CAT_CMD} ${FIREHOL_OUTPUT}
|
|
exit 1
|
|
fi
|
|
|
|
# apply the new ipsets
|
|
if [ ${ENABLE_IPSET} -eq 1 ]
|
|
then
|
|
ipsets_apply_all || exit 1
|
|
fi
|
|
|
|
syslog info "Activating new firewall from ${FIREHOL_CONFIG} (translated to ${FIREHOL_COMMAND_COUNTER} iptables rules)."
|
|
progress "Activating new firewall (${FIREHOL_COMMAND_COUNTER} rules)"
|
|
|
|
prepare_firewall_for_activation
|
|
|
|
file close 21
|
|
source "${FIREHOL_OUTPUT}" "${@}"
|
|
if [ $? -ne 0 ]
|
|
then
|
|
work_runtime_error=$[work_runtime_error+1]
|
|
else
|
|
finalize_activated_firewall
|
|
fi
|
|
fi
|
|
|
|
|
|
if [ ${work_runtime_error} -gt 0 ]
|
|
then
|
|
failure # "Activating new firewall"
|
|
syslog err "Activation of new firewall failed."
|
|
if [ $RUNNING_ON_TERMINAL -eq 0 ]
|
|
then
|
|
syslog err "To see errors, run manually: ${PROGRAM_FILE} ${PROGRAM_ORIGINAL_ARGS[@]}"
|
|
fi
|
|
|
|
# The trap will restore the firewall we saved above.
|
|
|
|
if [ ${FIREHOL_FAST_ACTIVATION} -eq 1 ]
|
|
then
|
|
echo >&2
|
|
echo >&2 "To get a more detailed report of the offending command,"
|
|
echo >&2 "you can quickly re-apply the same firewall with fast"
|
|
echo >&2 "activation disabled, like this:"
|
|
echo >&2
|
|
printf >&2 "${PROGRAM_FILE} nofast "
|
|
printf >&2 "%q " "${PROGRAM_ORIGINAL_ARGS[@]}"
|
|
printf >&2 "\n"
|
|
fi
|
|
|
|
exit 1
|
|
fi
|
|
success # "Activating new firewall (${FIREHOL_COMMAND_COUNTER} rules)"
|
|
|
|
file close 22
|
|
if [ -s "${FIREHOL_OUTPUT}.postprocess2" ]
|
|
then
|
|
progress "Executing postprocess2 commands"
|
|
${SH_CMD} "${FIREHOL_OUTPUT}.postprocess2"
|
|
success
|
|
fi
|
|
|
|
if [ ${FIREHOL_TRY} -eq 1 ]
|
|
then
|
|
syslog info "Waiting user to commit the new firewall."
|
|
read -p "Keep the firewall? (type 'commit' to accept - 30 seconds timeout) : " -t 30 -e
|
|
ret=$?
|
|
echo >&2
|
|
if [ ! $ret -eq 0 -o ! "${REPLY}" = "commit" ]
|
|
then
|
|
syslog err "User did not confirm the new firewall."
|
|
# The trap will restore the firewall.
|
|
|
|
exit 1
|
|
else
|
|
echo >&2 "Successful activation of FireHOL firewall."
|
|
syslog info "User committed new firewall."
|
|
fi
|
|
fi
|
|
|
|
# Remove the saved firewall, so that the trap will not restore it.
|
|
${RM_CMD} -f "${FIREHOL_SAVED}" "${FIREHOL_SAVED6}" >/dev/null 2>&1
|
|
FIREHOL_ACTIVATED_SUCCESSFULLY=1
|
|
|
|
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
# last, keep a copy of the firewall we activated, on disk
|
|
|
|
file close 20
|
|
$MV_CMD "${FIREHOL_DIR}/firewall_restore_commands.sh" "${FIREHOL_SPOOL_DIR}/firewall_restore_commands.sh"
|
|
$CHOWN_CMD root:root "${FIREHOL_SPOOL_DIR}/firewall_restore_commands.sh"
|
|
$CHMOD_CMD 700 "${FIREHOL_SPOOL_DIR}/firewall_restore_commands.sh"
|
|
|
|
# keep track if we do ipv4
|
|
if [ $ENABLE_IPV4 -eq 1 ]
|
|
then
|
|
$TOUCH_CMD "${FIREHOL_SPOOL_DIR}/ipv4.enable"
|
|
$CHOWN_CMD root:root "${FIREHOL_SPOOL_DIR}/ipv4.enable"
|
|
$CHMOD_CMD 600 "${FIREHOL_SPOOL_DIR}/ipv4.enable"
|
|
else
|
|
test -f "${FIREHOL_SPOOL_DIR}/ipv4.enable" && $RM_CMD "${FIREHOL_SPOOL_DIR}/ipv4.enable"
|
|
fi
|
|
|
|
# keep track if we do ipv6
|
|
if [ $ENABLE_IPV6 -eq 1 ]
|
|
then
|
|
$TOUCH_CMD "${FIREHOL_SPOOL_DIR}/ipv6.enable"
|
|
$CHOWN_CMD root:root "${FIREHOL_SPOOL_DIR}/ipv6.enable"
|
|
$CHMOD_CMD 600 "${FIREHOL_SPOOL_DIR}/ipv6.enable"
|
|
else
|
|
test -f "${FIREHOL_SPOOL_DIR}/ipv6.enable" && $RM_CMD "${FIREHOL_SPOOL_DIR}/ipv6.enable"
|
|
fi
|
|
|
|
# save the rules to ${FIREHOL_SPOOL_DIR}
|
|
firehol_save_activated_firewall
|
|
|
|
|
|
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
|
|
if [ ${FIREHOL_SAVE} -eq 1 ]
|
|
then
|
|
if [ $ENABLE_IPV4 -eq 1 -a -z "${FIREHOL_AUTOSAVE}" ]
|
|
then
|
|
if [ -d "/etc/sysconfig" ]
|
|
then
|
|
# RedHat
|
|
FIREHOL_AUTOSAVE="/etc/sysconfig/iptables"
|
|
elif [ -d "/var/lib/iptables" ]
|
|
then
|
|
if [ -f /etc/conf.d/iptables ]
|
|
then
|
|
# Gentoo
|
|
IPTABLES_SAVE=
|
|
|
|
. /etc/conf.d/iptables
|
|
FIREHOL_AUTOSAVE="${IPTABLES_SAVE}"
|
|
fi
|
|
|
|
if [ -z "${FIREHOL_AUTOSAVE}" ]
|
|
then
|
|
# Debian
|
|
FIREHOL_AUTOSAVE="/var/lib/iptables/autosave"
|
|
fi
|
|
else
|
|
error "Cannot find where to save iptables file. Please set FIREHOL_AUTOSAVE."
|
|
echo >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
if [ $ENABLE_IPV6 -eq 1 -a -z "${FIREHOL_AUTOSAVE6}" ]
|
|
then
|
|
error "Cannot find where to save ip6tables file. Please set FIREHOL_AUTOSAVE6."
|
|
echo >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ $ENABLE_IPV4 -eq 1 ]
|
|
then
|
|
progress "Saving firewall to '${FIREHOL_AUTOSAVE}'"
|
|
|
|
$CAT_CMD "${FIREHOL_SPOOL_DIR}/ipv4.rules" >${FIREHOL_AUTOSAVE}
|
|
if [ ! $? -eq 0 ]
|
|
then
|
|
syslog err "Failed to save new firewall to '${FIREHOL_AUTOSAVE}'."
|
|
failure # "Saving firewall to '${FIREHOL_AUTOSAVE}'"
|
|
exit 1
|
|
fi
|
|
|
|
syslog info "New firewall saved to '${FIREHOL_AUTOSAVE}'."
|
|
success # "Saving firewall to '${FIREHOL_AUTOSAVE}'"
|
|
fi
|
|
|
|
if [ $ENABLE_IPV6 -eq 1 ]
|
|
then
|
|
progress "Saving IPv6 firewall to '${FIREHOL_AUTOSAVE6}'"
|
|
|
|
$CAT_CMD "${FIREHOL_SPOOL_DIR}/ipv6.rules" >${FIREHOL_AUTOSAVE6}
|
|
if [ ! $? -eq 0 ]
|
|
then
|
|
syslog err "Failed to save new IPv6 firewall to '${FIREHOL_AUTOSAVE6}'."
|
|
failure # "Saving IPv6 firewall to '${FIREHOL_AUTOSAVE6}'"
|
|
exit 1
|
|
fi
|
|
|
|
syslog info "New IPv6 firewall saved to '${FIREHOL_AUTOSAVE6}'."
|
|
success # "Saving IPv6 firewall to '${FIREHOL_AUTOSAVE6}'"
|
|
fi
|
|
|
|
exit 0
|
|
fi
|