#!/bin/bash # # FireHOL - A firewall for humans... # # Copyright # # Copyright (C) 2003-2015 Costa Tsaousis # Copyright (C) 2012-2015 Phil Whineray # # 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 . # # See the file COPYING for details. # if [ $(( ${BASH_VERSINFO[0]} )) -lt 4 ] then echo >&2 echo >&2 "ERROR:" echo >&2 "FireHOL requires BASH version 4 or later." echo >&2 "You are running version: ${BASH_VERSION}" echo >&2 "Please upgrade." echo >&2 exit 1 fi get_version() { GIT_REF='$Format:%d,commit-%h$' local IFS=":(), " set -- "$GIT_REF" ver='$Id$' for i in ${@} do case "$i" in *[0-9].[0.9]*) echo "$i" | sed -e 's/^v//' return 0 ;; commit-[0-9a-zA-Z]*) ver="$i" ;; esac done echo "$ver" return 0 } VERSION=$(get_version) emit_version() { ${CAT_CMD} < (C) Copyright 2012-2014 Phil Whineray 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 } # Make sure only root can run us. if [ ! "${UID}" = 0 ] then echo >&2 echo >&2 "ERROR:" echo >&2 "Only user root can run FireHOL." echo >&2 exit 1 fi # Remember who you are. PROGRAM_FILE="${0}" declare -a FIREHOL_ORIGINAL_ARGS=("${@}") FIREHOL_DEFAULT_WORKING_DIRECTORY="${PWD}" # Make sure we don't get localized results export LC_ALL=C if [ "z$1" = "z-nc" ] then shift else # Enable colors if [ $[$(tput colors 2>/dev/null)] -ge 8 ] then FIREHOL_ENABLE_SPINNER=0 COLOR_RESET="\e[0m" COLOR_BLACK="\e[30m" COLOR_RED="\e[31m" COLOR_GREEN="\e[32m" COLOR_YELLOW="\e[33m" COLOR_BLUE="\e[34m" COLOR_PURPLE="\e[35m" COLOR_CYAN="\e[36m" COLOR_WHITE="\e[37m" COLOR_BGBLACK="\e[40m" COLOR_BGRED="\e[41m" COLOR_BGGREEN="\e[42m" COLOR_BGYELLOW="\e[43m" COLOR_BGBLUE="\e[44m" COLOR_BGPURPLE="\e[45m" COLOR_BGCYAN="\e[46m" COLOR_BGWHITE="\e[47m" COLOR_BOLD="\e[1m" COLOR_DIM="\e[2m" COLOR_UNDERLINED="\e[4m" COLOR_BLINK="\e[5m" COLOR_INVERTED="\e[7m" fi fi # ------------------------------------------------------------------------------ # 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 'marksreset' 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 # prevent a fork 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 /etc/firehol FIREHOL_CONFIG_DIR="/etc/firehol" # 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: /etc/firehol/services FIREHOL_SERVICES_DIR="${FIREHOL_CONFIG_DIR}/services" # Where to permanently save state information? # Default: /var/spool/firehol FIREHOL_SPOOL_DIR="/var/spool/firehol" # Where temporary files should go? # /var/run is usualy a ram drive, so we prefer to use # this for temporary files. # Default: /var/run/firehol FIREHOL_RUN_DIR="/var/run/firehol" # 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 successfuly # 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="1" # Enable IPv6 firewall # Default: 1 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' (successfull 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_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', logginng 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. # Default: 1 FIREHOL_DROP_INVALID=1 # 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 enabled FireHOL will create a chain per server/client statement and jump # to it from the main flow. Normaly 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 assignement? # # '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. # CAUSION: 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 assignement manually using # the 'cthelper' firehol helper. # In this case, neither the kernel nor FireHOL will do anything # about conntrack helpers assignement. # # Default: kernel FIREHOL_CONNTRACK_HELPERS_ASSIGNMENT="kernel" # If set to 0, FireHOL will NOT trust interface lo for all traffic, thus # a firewall could be set up on lo. # Default: 1 FIREHOL_TRUST_LOOPBACK=1 # 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: # Possible Values: 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" # ---------------------------------------------------------------------- # 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: 0 FIREHOL_CONNTRACK_LOOSE_MATCHING=0 # 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: 65536 FIREHOL_CONNTRACK_MAX="65536" # 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: 16384 FIREHOL_CONNTRACK_HASHSIZE="16384" # required for synproxy # This will be automatically set to 1 if you use the synproxy helper. # This sets net.ipv4.tcp_syncookies=1 # Default: 0 FIREHOL_TCP_SYN_COOKIES=0 # required for synproxy # This will be automatically set to 1 if you use the synproxy helper. # This sets net.ipv4.tcp_timestamps=1 # Default: 0 FIREHOL_TCP_TIMESTAMPS=0 # ---------------------------------------------------------------------- # 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 # 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 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_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 overwite these settings with the contents of the files with # the same names in ${FIREHOL_CONFIG_DIR}. # # For example, RESERVED_IPV4 will be set from /etc/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 [ -f "${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 # save the 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" # 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 # ------------------------------------------------------------------------------ export PATH="${PATH}:/bin:/usr/bin:/sbin:/usr/sbin" # External commands FireHOL will need. # If one of those is not found, FireHOL will refuse to run. which_cmd() { local cmd= block=1 if [ "a${1}" = "a-n" ] then block=0 shift fi unalias $2 >/dev/null 2>&1 cmd=`which $2 2>/dev/null | head -n 1` if [ $? -gt 0 -o ! -x "${cmd}" ] then if [ ${block} -eq 1 ] then echo >&2 echo >&2 "ERROR: Command '$2' not found in the system path." echo >&2 " FireHOL requires this command for its operation." echo >&2 " Please install the required package and retry." echo >&2 echo >&2 " Note that you need an operational 'which' command" echo >&2 " for FireHOL to find all the external programs it" echo >&2 " needs. Check it yourself. Run:" echo >&2 echo >&2 " which $2" exit 1 fi return 1 fi eval $1=${cmd} return 0 } # command on demand support. require_cmd() { local var= val= block=1 if [ "a$1" = "a-n" ] then block=0 shift fi # if one is found, return success for x in "${@}" do eval var=`echo ${x} | tr 'a-z-' 'A-Z_'`_CMD eval val=\$\{${var}\} if [ -z "${val}" ] then which_cmd -n "${var}" "${x}" test $? -eq 0 && return 0 fi done if [ $block -eq 1 ] then echo >&2 echo >&2 "ERROR: FIREHOL REQUIRES THESE COMMANDS:" echo >&2 echo >&2 " ${@}" echo >&2 echo >&2 " You have requested the use of an optional FireHOL" echo >&2 " feature that requires certain external programs" echo >&2 " to be installed in the running system." echo >&2 echo >&2 " Please consult your Linux distribution manual to" echo >&2 " install the package(s) that provide these external" echo >&2 " programs and retry." echo >&2 echo >&2 " Note that you need an operational 'which' command" echo >&2 " for FireHOL to find all the external programs it" echo >&2 " needs. Check it yourself. Run:" echo >&2 for x in "${@}" do echo >&2 " which $x" done exit 1 fi return 1 } # Currently the following commands are required only when needed. # (i.e. Command on Demand) # # zcat or gzcat or gzip (either or none is fine) # less or more (either or none is fine) # ip # ss # date # hostname # modprobe or insmod # gawk or awk # nice (none is fine) # Commands that are mandatory for FireHOL operation: which_cmd CAT_CMD cat which_cmd CUT_CMD cut which_cmd CHOWN_CMD chown which_cmd CHMOD_CMD chmod which_cmd EGREP_CMD egrep which_cmd EXPR_CMD expr which_cmd FIND_CMD find which_cmd FOLD_CMD fold which_cmd GREP_CMD grep which_cmd HEAD_CMD head which_cmd TAIL_CMD tail which_cmd LSMOD_CMD lsmod which_cmd MKDIR_CMD mkdir which_cmd MKTEMP_CMD mktemp which_cmd MV_CMD mv which_cmd RM_CMD rm which_cmd SED_CMD sed which_cmd SORT_CMD sort which_cmd SYSCTL_CMD sysctl which_cmd TOUCH_CMD touch which_cmd TR_CMD tr which_cmd UNAME_CMD uname which_cmd UNIQ_CMD uniq which_cmd LOGGER_CMD logger which_cmd FLOCK_CMD flock ENABLE_ACCOUNTING=1 ACCOUNTING_WARNING=0 require_cmd -n nfacct 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 require_cmd -n ipset 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 require_cmd -n iptables require_cmd -n iptables-save require_cmd -n iptables-restore 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 require_cmd -n ip6tables require_cmd -n ip6tables-save require_cmd -n ip6tables-restore 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 # Special commands pager_cmd() { if [ -z "${LESS_CMD}" ] then require_cmd -n less more test -z "${LESS_CMD}" && LESS_CMD="${MORE_CMD}" test -z "${LESS_CMD}" && LESS_CMD="${CAT_CMD}" fi "${LESS_CMD}" "${@}" } zcat_cmd() { require_cmd -n zcat gzcat gzip test -z "${ZCAT_CMD}" && ZCAT_CMD="${GZCAT_CMD}" if [ ! -z "${ZCAT_CMD}" ] then "${ZCAT_CMD}" "${@}" return $? elif [ ! -z "${GZIP_CMD}" ] then "${CAT_CMD}" "${@}" | "${GZIP_CMD}" -dc return $? fi echo >&2 " " echo >&2 " WARNING:" echo >&2 " --------" echo >&2 " FireHOL cannot find any of the commands: zcat, gzcat, gzip." echo >&2 " Make sure you have one of these available in the system path." echo >&2 " " return 1 } gawk_cmd() { require_cmd -n gawk awk test -z "${GAWK_CMD}" && GAWK_CMD="${AWK_CMD}" if [ ! -z "${GAWK_CMD}" ] then "${GAWK_CMD}" "${@}" return $? fi echo >&2 " " echo >&2 " WARNING:" echo >&2 " --------" echo >&2 " FireHOL cannot find any of the commands: gawk, awk." echo >&2 " Make sure you have one of these available in the system path." echo >&2 " " return 1 } modprobe_cmd() { require_cmd -n modprobe insmod test -z "${MODPROBE_CMD}" && MODPROBE_CMD="${INSMOD_CMD}" if [ ! -z "${MODPROBE_CMD}" ] then save_for_restore none "${MODPROBE_CMD}" "${@}" "${MODPROBE_CMD}" "${@}" status=$? if [ $status -eq 17 ] then # insmod: module already loaded - not a problem return 0 else return $status fi fi echo >&2 " " echo >&2 " WARNING:" echo >&2 " --------" echo >&2 " FireHOL cannot find any of the commands: modprobe, insmod." echo >&2 " Make sure you have one of these available in the system path." echo >&2 " " return 1 } renice_cmd() { if [ -z "${RENICE_CMD}" ] then require_cmd -n renice test -z "${RENICE_CMD}" && RENICE_CMD=":" fi "${RENICE_CMD}" "${@}" } 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 } # Make sure our generated files cannot be accessed by anyone else. umask 077 # Be nice on production environments renice_cmd 10 $$ >/dev/null 2>/dev/null # Initialize iptables if [ $ENABLE_IPV4 -eq 1 ] then ${IPTABLES_CMD} -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 ${IP6TABLES_CMD} -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_exit() { local restored="NO" 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 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 \"$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 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 >&20 <"${FIREHOL_CONFIG_DIR}/firehol-defaults.conf" || exit 1 "${CHOWN_CMD}" root:root "${FIREHOL_CONFIG_DIR}/firehol-defaults.conf" || exit 1 "${CHMOD_CMD}" 600 "${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 } # TODO: # This is is problematic when ENABLE_IPx=0 # is given in the config file and not in the # defaults 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 # 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 # 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 # # to have FireHOL require a specific module for the configurarion. # Services may add themeselves to this variable so that the service "all" will # also call them. # By default it is empty - only rules programmers should change this. ALL_SHOULD_ALSO_RUN= # ------------------------------------------------------------------------------ # 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} FIREHOL_NS_PREP= 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() { local command="$1" shift if [ "${command}" = "interface" -o "${command}" = "router" ] then # Old rules must be closed before these apply FIREHOL_NS_PREP=ipv4 $command "${@}" status=$? else if ! push_namespace ipv4; then return 1; fi $command "${@}" status=$? pop_namespace fi return $status } ipv6() { local command="$1" shift if [ "${command}" = "interface" -o "${command}" = "router" ] then # Old rules must be closed before these apply FIREHOL_NS_PREP=ipv6 $command "${@}" status=$? else if ! push_namespace ipv6; then return 1; fi $command "${@}" status=$? pop_namespace fi return $status } both() { local command="$1" shift if [ "${command}" = "interface" -o "${command}" = "router" ] then # Old rules must be closed before these apply FIREHOL_NS_PREP=${FIREHOL_DEFAULT_NAMESPACE} $command "${@}" status=$? else if ! push_namespace both; then return 1; fi $command "${@}" status=$? pop_namespace fi return $status } # ------------------------------------------------------------------------------ # 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 } # Services API version FIREHOL_SERVICES_API="1" # ------------------------------------------------------------------------------ # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # ------------------------------------------------------------------------------ # # SIMPLE SERVICES DEFINITIONS # # ------------------------------------------------------------------------------ # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # ------------------------------------------------------------------------------ # The following are definitions for simple services. # We define as "simple" the services that are implemented using a single socket, # initiated by the client and used by the server. # # The following list is sorted by service name. server_AH_ports="51/any" client_AH_ports="any" server_amanda_ports="udp/10080" client_amanda_ports="default" helper_amanda="amanda" server_aptproxy_ports="tcp/9999" client_aptproxy_ports="default" server_apcupsd_ports="tcp/6544" client_apcupsd_ports="default" server_apcupsdnis_ports="tcp/3551" client_apcupsdnis_ports="default" server_asterisk_ports="tcp/5038" client_asterisk_ports="default" server_cups_ports="tcp/631 udp/631" client_cups_ports="any" server_cvspserver_ports="tcp/2401" client_cvspserver_ports="default" server_darkstat_ports="tcp/666" client_darkstat_ports="default" server_daytime_ports="tcp/13" client_daytime_ports="default" server_dcc_ports="udp/6277" client_dcc_ports="default" server_dcpp_ports="tcp/1412 udp/1412" client_dcpp_ports="default" server_dns_ports="udp/53 tcp/53" client_dns_ports="any" server_dhcprelay_ports="udp/67" client_dhcprelay_ports="67" server_dict_ports="tcp/2628" client_dict_ports="default" server_distcc_ports="tcp/3632" client_distcc_ports="default" server_eserver_ports="tcp/4661 udp/4661 udp/4665" client_eserver_ports="any" server_ESP_ports="50/any" client_ESP_ports="any" server_echo_ports="tcp/7" client_echo_ports="default" server_finger_ports="tcp/79" client_finger_ports="default" server_ftp_ports="tcp/21" client_ftp_ports="default" helper_ftp="ftp" ALL_SHOULD_ALSO_RUN="${ALL_SHOULD_ALSO_RUN} ftp" server_gift_ports="tcp/4302 tcp/1214 tcp/2182 tcp/2472" client_gift_ports="any" server_giftui_ports="tcp/1213" client_giftui_ports="default" server_gkrellmd_ports="tcp/19150" client_gkrellmd_ports="default" server_GRE_ports="47/any" client_GRE_ports="any" helper_GRE="proto_gre" server_h323_ports="udp/1720 tcp/1720" client_h323_ports="default" helper_h323="h323" # ALL_SHOULD_ALSO_RUN="${ALL_SHOULD_ALSO_RUN} h323" server_heartbeat_ports="udp/690:699" client_heartbeat_ports="default" server_http_ports="tcp/80" client_http_ports="default" server_https_ports="tcp/443" client_https_ports="default" server_httpalt_ports="tcp/8080" client_httpalt_ports="default" server_iax_ports="udp/5036" client_iax_ports="default" server_iax2_ports="udp/5469 udp/4569" client_iax2_ports="default" server_ICMP_ports="icmp/any" client_ICMP_ports="any" server_icmp_ports="${server_ICMP_ports}" client_icmp_ports="${client_ICMP_ports}" # ALL_SHOULD_ALSO_RUN="${ALL_SHOULD_ALSO_RUN} icmp" server_ICMPV6_ports="icmpv6/any" client_ICMPV6_ports="any" server_icmpv6_ports="${server_ICMPV6_ports}" client_icmpv6_ports="${client_ICMPV6_ports}" server_icp_ports="udp/3130" client_icp_ports="3130" server_ident_ports="tcp/113" client_ident_ports="default" server_imap_ports="tcp/143" client_imap_ports="default" server_imaps_ports="tcp/993" client_imaps_ports="default" server_irc_ports="tcp/6667" client_irc_ports="default" helper_irc="irc" ALL_SHOULD_ALSO_RUN="${ALL_SHOULD_ALSO_RUN} irc" server_isakmp_ports="udp/500" client_isakmp_ports="any" server_ipsecnatt_ports="udp/4500" client_ipsecnatt_ports="any" server_jabber_ports="tcp/5222 tcp/5223" client_jabber_ports="default" server_jabberd_ports="tcp/5222 tcp/5223 tcp/5269" client_jabberd_ports="default" server_l2tp_ports="udp/1701" client_l2tp_ports="any" server_ldap_ports="tcp/389" client_ldap_ports="default" server_ldaps_ports="tcp/636" client_ldaps_ports="default" server_lpd_ports="tcp/515" client_lpd_ports="any" server_microsoft_ds_ports="tcp/445" client_microsoft_ds_ports="default" server_mms_ports="tcp/1755 udp/1755" client_mms_ports="default" helper_mms="mms" # this will produce warnings on most distribution # because the mms module is not there: # ALL_SHOULD_ALSO_RUN="${ALL_SHOULD_ALSO_RUN} mms" server_ms_ds_ports="${server_microsoft_ds_ports}" client_ms_ds_ports="${client_microsoft_ds_ports}" server_msnp_ports="tcp/6891" client_msnp_ports="default" server_msn_ports="tcp/1863 udp/1863" client_msn_ports="default" server_mysql_ports="tcp/3306" client_mysql_ports="default" server_netbackup_ports="tcp/13701 tcp/13711 tcp/13720 tcp/13721 tcp/13724 tcp/13782 tcp/13783" client_netbackup_ports="any" server_netbios_ns_ports="udp/137" client_netbios_ns_ports="any" server_netbios_dgm_ports="udp/138" client_netbios_dgm_ports="any" server_netbios_ssn_ports="tcp/139" client_netbios_ssn_ports="default" server_nntp_ports="tcp/119" client_nntp_ports="default" server_nntps_ports="tcp/563" client_nntps_ports="default" server_ntp_ports="udp/123 tcp/123" client_ntp_ports="any" server_nut_ports="tcp/3493 udp/3493" client_nut_ports="default" server_nxserver_ports="tcp/5000:5200" client_nxserver_ports="default" server_openvpn_ports="tcp/1194 udp/1194" client_openvpn_ports="default" server_oracle_ports="tcp/1521" client_oracle_ports="default" server_OSPF_ports="89/any" client_OSPF_ports="any" server_pop3_ports="tcp/110" client_pop3_ports="default" server_pop3s_ports="tcp/995" client_pop3s_ports="default" server_portmap_ports="udp/111 tcp/111" client_portmap_ports="any" # Portmap clients appear to use ports below 1024 server_postgres_ports="tcp/5432" client_postgres_ports="default" server_pptp_ports="tcp/1723" client_pptp_ports="default" helper_pptp="pptp proto_gre" # ALL_SHOULD_ALSO_RUN="${ALL_SHOULD_ALSO_RUN} pptp" server_privoxy_ports="tcp/8118" client_privoxy_ports="default" server_radius_ports="udp/1812 udp/1813" client_radius_ports="default" server_radiusproxy_ports="udp/1814" client_radiusproxy_ports="default" server_radiusold_ports="udp/1645 udp/1646" client_radiusold_ports="default" server_radiusoldproxy_ports="udp/1647" client_radiusoldproxy_ports="default" server_rdp_ports="tcp/3389" client_rdp_ports="default" server_rndc_ports="tcp/953" client_rndc_ports="default" server_rsync_ports="tcp/873 udp/873" client_rsync_ports="default" server_rtp_ports="udp/10000:20000" client_rtp_ports="any" server_sane_ports="tcp/6566" client_sane_ports="default" helper_sane="sane" server_sip_ports="tcp/5060 udp/5060" client_sip_ports="5060 default" helper_sip="sip" # ALL_SHOULD_ALSO_RUN="${ALL_SHOULD_ALSO_RUN} sip" server_socks_ports="tcp/1080 udp/1080" client_socks_ports="default" server_squid_ports="tcp/3128" client_squid_ports="default" server_smtp_ports="tcp/25" client_smtp_ports="default" server_smtps_ports="tcp/465" client_smtps_ports="default" server_snmp_ports="udp/161" client_snmp_ports="default" server_snmptrap_ports="udp/162" client_snmptrap_ports="any" server_nrpe_ports="tcp/5666" client_nrpe_ports="default" server_ssh_ports="tcp/22" client_ssh_ports="default" server_stun_ports="udp/3478 udp/3479" client_stun_ports="any" server_submission_ports="tcp/587" client_submission_ports="default" server_sunrpc_ports="${server_portmap_ports}" client_sunrpc_ports="${client_portmap_ports}" server_swat_ports="tcp/901" client_swat_ports="default" server_syslog_ports="udp/514" client_syslog_ports="514 default" server_telnet_ports="tcp/23" client_telnet_ports="default" server_tftp_ports="udp/69" client_tftp_ports="default" helper_tftp="tftp" # ALL_SHOULD_ALSO_RUN="${ALL_SHOULD_ALSO_RUN} tftp" server_tomcat_ports="${server_httpalt_ports}" client_tomcat_ports="${client_httpalt_ports}" server_time_ports="tcp/37 udp/37" client_time_ports="default" server_upnp_ports="udp/1900 tcp/2869" client_upnp_ports="default" server_uucp_ports="tcp/540" client_uucp_ports="default" server_whois_ports="tcp/43" client_whois_ports="default" server_vmware_ports="tcp/902" client_vmware_ports="default" server_vmwareauth_ports="tcp/903" client_vmwareauth_ports="default" server_vmwareweb_ports="tcp/8222 tcp/8333" client_vmwareweb_ports="default" server_vnc_ports="tcp/5900:5903" client_vnc_ports="default" server_webcache_ports="${server_httpalt_ports}" client_webcache_ports="${client_httpalt_ports}" server_webmin_ports="tcp/10000" client_webmin_ports="default" server_xdmcp_ports="udp/177" client_xdmcp_ports="default" # ------------------------------------------------------------------------------ # 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 bellow 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 \ mychain="${1}" \ type="${2}" \ icmpv6in="${3}" \ icmpv6out="${4}" shift 4 if [ "${type}" = "client" ] then in=out out=in fi rule ${in} action "${@}" chain "${in}_${mychain}" proto icmpv6 custom "--icmpv6-type $icmpv6in" || return 1 rule ${out} reverse action "${@}" chain "${out}_${mychain}" proto icmpv6 custom "--icmpv6-type $icmpv6out" || return 1 return 0 } add_icmpv6_rule_error() { # Unlike stateful and stateless icmpv6 packets, for a server # or client we do the same thing: # ingress error packets allowed if we think we have a connection # egress error packets allowed whatever # Possibly we could restrict the whatever to be related also? local mychain="${1}" \ type="${2}" \ icmpv6error="${3}" shift 3 local in=in out=out if [ "${type}" = "client" ] then in=out out=in fi rule ${in} reverse action "${@}" chain "${in}_${mychain}" proto icmpv6 custom "--icmpv6-type $icmpv6error" state ESTABLISHED,RELATED || return 1 rule ${out} action "${@}" chain "${out}_${mychain}" proto icmpv6 custom "--icmpv6-type $icmpv6error" || return 1 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 "Setting up 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 "Setting up 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 "Setting up 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 incomming to server tcp/4662 set_work_function "Setting up 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 "Setting up 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 incomming to server udp/4672 set_work_function "Setting up 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 "Setting up 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 incomming to server tcp/4661 set_work_function "Setting up 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 incomming to server udp/4665 set_work_function "Setting up 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 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 incomming to server tcp/4559 set_work_function "Setting up 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 "Setting up 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 "Setting up 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. # Bellow 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 "Setting up 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 "Setting up 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 "Setting up 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 [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 "Processing rquotad rules for server '${x}'" rules_custom "${mychain}" "${type}" nfs-rquotad "${server_rquotad_ports}" "500:65535" "${action}" $dst "${@}" fi set_work_function "Processing mountd rules for server '${x}'" rules_custom "${mychain}" "${type}" nfs-mountd "${server_mountd_ports}" "500:65535" "${action}" $dst "${@}" set_work_function "Processing lockd rules for server '${x}'" rules_custom "${mychain}" "${type}" nfs-lockd "${server_lockd_ports}" "500:65535" "${action}" $dst "${@}" set_work_function "Processing statd rules for server '${x}'" rules_custom "${mychain}" "${type}" nfs-statd "${server_statd_ports}" "500:65535" "${action}" $dst "${@}" set_work_function "Processing nfsd rules for 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 # Feature Requests item #1050951 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 [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 "Processing yppasswd rules for server '${x}'" rules_custom "${mychain}" "${type}" nis-yppasswd "${server_yppasswdd_ports}" "500:65535" "${action}" $dst "${@}" fi set_work_function "Processing ypserv rules for 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 } # --- IVP6ERROR ---------------------------------------------------------------- rules_ipv6error() { local mychain="${1}" \ type="${2}" shift 2 if ! push_namespace ipv6; then return 1; fi for icmptype in destination-unreachable \ packet-too-big \ ttl-zero-during-transit \ ttl-zero-during-reassembly \ unknown-header-type \ unknown-option do add_icmpv6_rule_error $mychain $type $icmptype "${@}" if [ $? -ne 0 ] then pop_namespace return 1 fi done pop_namespace return 0 } # --- ALL ---------------------------------------------------------------------- rules_all() { 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 new and established incoming packets rule ${in} action "${@}" chain "${in}_${mychain}" state NEW,ESTABLISHED || return 1 # allow outgoing established packets rule ${out} reverse action "${@}" chain "${out}_${mychain}" state ESTABLISHED || return 1 local ser= for ser in ${ALL_SHOULD_ALSO_RUN} do "${type}" ${ser} "${@}" || return 1 done return 0 } # --- ANY ---------------------------------------------------------------------- rules_any() { local in=in out=out \ client_ports="${DEFAULT_CLIENT_PORTS}" \ mychain="${1}" \ type="${2}" \ name="${3}" shift 3 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}" state NEW,ESTABLISHED || return 1 # allow outgoing established packets rule ${out} reverse action "${@}" chain "${out}_${mychain}" state ESTABLISHED || return 1 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 "Configuring helper '${x}' for this flow of traffic" # 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 matching 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). rule ${in} action "${@}" chain "${in}_${mychain}" helper ${x} state RELATED || return 1 rule ${out} reverse action "${@}" chain "${out}_${mychain}" helper ${x} state RELATED || 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 *.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 "${FIREHOL_DEFAULT_WORKING_DIRECTORY}" || 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 BELLOW THIS POINT # # ------------------------------------------------------------------------------ # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # ------------------------------------------------------------------------------ FIREHOL_SYNPROXY_COUNTER=1 synproxy4() { ipv4 synproxy "${@}"; } synproxy6() { ipv6 synproxy "${@}"; } synproxy46() { both synproxy "${@}"; } synproxy() { work_realcmd_helper ${FUNCNAME} "${@}" local where="${1}" chain= 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 ) set_work_function -ne "Untracking the SYNPROXY packets in table 'raw'" rule table raw chain PREROUTING insert_as ${FIREHOL_SYNPROXY_COUNTER} "${@}" proto tcp custom '-m tcp --syn' action CT --notrack || return 1 for chain in ${where//,/ } do case "${chain^^}" in PRE|PREROUTING) error "There is no PREROUTING chain in table filter"; return 1 ;; IN|INPUT) chain="INPUT" ;; OUT|OUTPUT) error "There is no point to setup SYNPROXY on OUTPUT"; return 1 ;; PASS|FORWARD) chain="FORWARD" ;; POST|POSTROUTING) error "There is no POSTROUTING chain in table filter"; return 1 ;; esac # FIXME # most probably we will have to support tcp options per call for this helper set_work_function -ne "Sending TCP packets to SYNPROXY" rule table filter chain ${chain} insert_as ${FIREHOL_SYNPROXY_COUNTER} "${@}" proto tcp custom '-m tcp' state INVALID,UNTRACKED action SYNPROXY --sack-perm --timestamp --wscale 7 --mss 1460 || return 1 done (( FIREHOL_SYNPROXY_COUNTER += 1 )) 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 create_chain filter ${name} || return 1 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 "${FUNCNAME}: rules for type ${type} under table ${t}: ${args[@]}" rule table ${t} chain "${name}" action "${args[0]}" || return 1 done ;; rule) for t in ${tables//,/ } do set_work_function "${FUNCNAME}: 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 "${FUNCNAME}: 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 "${FUNCNAME}: rules for type ${type} under table ${t}: ${args[@]}" ipuntrap "${ipt1}" "${ipt2}" chain "${name}" table ${t} "${args[@]}" || 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 "Initializing masquerade on interface '${f}'" rule noowner table nat chain POSTROUTING "${@}" inface any outface "${f}" 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 "Setting up 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 "Setting up 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 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 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() { require_cmd ip 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 mask 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 #FIREHOL_TPROXY_MARK="$[ MARKS_MAX[usermark] << MARKS_SHIFT[usermark] ]/${MARKS_MASKS[usermark]}" FIREHOL_TPROXY_MARK="$(mark_value usermark MARKS_MAX[usermark])" 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 "Setting up 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 } 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}" \ chain= \ action= \ options= \ requirements= \ overwrite= shift 2 while [ ! -z "${1}" ] do case "${1}" in random|--random) options="${options} random" shift ;; persistent|--persistent) # REDIRECT does not support persistence, but anyway # if the user gives this on REDIRECT, he/she will get # an error later options="${options} persistent" shift ;; *) break ;; esac done nat_count=$[nat_count + 1] set_work_function -ne "Setting up rules for NAT type: '${type}'" case ${type} in to-source) #create_chain nat "nat.${nat_count}" POSTROUTING nolog "${@}" inface any || return 1 chain=POSTROUTING action=snat overwrite="inface any" ;; to-destination) #create_chain nat "nat.${nat_count}" PREROUTING noowner nolog "${@}" outface any || return 1 chain=PREROUTING action=dnat requirements=noowner overwrite="outface any" ;; redirect-to) #create_chain nat "nat.${nat_count}" PREROUTING noowner nolog "${@}" outface any || return 1 chain=PREROUTING action=redirect requirements=noowner overwrite="outface any" ;; *) 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 set_work_function "Taking the NAT action: '${action}'" # we now need to keep the protocol # rule table nat chain "nat.${nat_count}" noowner "${@}" nosoftwarnings src any dst any inface any outface any sport any dport any action "${action}" to "${to}" ${options} || return 1 rule table nat chain "${chain}" ${requirements} "${@}" ${overwrite} action "${action}" to "${to}" ${options} || return 1 FIREHOL_NAT=1 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 "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 "MAC MISSMATCH" action DROP || return 1 wrongmac6_chain=1 fi fi set_work_function "If the source IP ${1} does not match MAC ${2}, drop the packet" 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 } # 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 ;; *) ;; esac while [ ! -z "${1}" ] do case "${1,,}" in src) shift ;; except) shift break ;; log) logopts_in_arg=(log "${2}-IN") logopts_out_arg=(log "${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) inface=(inface "${2}") shift 2 ;; *) 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 "Generating blacklist input chain" # create the input chain (common for both stateless and stateful) iptables_both -t filter -N "${chain}.in" # add the excepted rules test ! -z "${1}" && ( rule table filter chain "${chain}.in" in action RETURN "${@}" || return 1 ) # add the accounting rules test ! -z "${accounting}" && ( iptables_both -t filter -A "${chain}.in" -m nfacct --nfacct-name "${accounting}" || return 1 ) # 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 "Generating 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 # bi-directional stateless # no traffic from/to them set_work_function "Blacklist input from them" for x in INPUT FORWARD do rule table filter chain ${x} in "${inface[@]}" src "${src[*]}" action "${chain}.in" || return 1 done set_work_function "Generating output chain" # create the output chain iptables_both -t filter -N "${chain}.out" # add the excepted rules test ! -z "${1}" && ( rule table filter chain "${chain}.out" out reverse action RETURN "${@}" || return 1 ) # add the accounting rules test ! -z "${accounting}" && ( iptables_both -t filter -A "${chain}.out" -m nfacct --nfacct-name "${accounting}" || return 1 ) if running_ipv4 then push_namespace ipv4 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 ipv4 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 "Generating 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[*]}" 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) method="${2}" shift ;; counters|COUNTERS) mode="COUNTERS" ;; timeout|TIMEOUT) mode="TIMEOUT" ;; chain|CHAIN) link_to="${2}" shift ;; table|tables|TABLE|TABLES) tables="${2}" shift ;; action|ACTION) action="${2}" shift ;; except|EXCEPT) shift break ;; log|loglimit|LOG|LOGLIMIT) 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 does not exist, create it if [ -z "${FIREHOL_IPSETS_USED[$ipset]}" ] 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 "Generating trap chain ${chain} in table ${t}" # create the chain iptables_both -t ${t} -N "${chain}" # add the excepted rules test ! -z "${1}" && ( rule table ${t} chain "${chain}" in action RETURN "${@}" || return 1 ) # 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 "Generating iptrap matching 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 "Setting up 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 "Setting up 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 "Setting up 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 "Setting up 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 "Setting up 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 "Setting up 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 "Setting up 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 "Setting up 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 "Creating 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 "Creating 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 "Setting up rules for setting 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 "Initializing 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 "Initializing 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 } ipset_addfile() { local name="${1}" file= opts= final_cmd="${CAT_CMD}" ipv_match="^[0-9a-fA-F\.:/\-]+$" shift while [ ! -z "${1}" ] do case "${1}" in ip|ips) final_cmd="${GREP_CMD} -v /";; net|nets) final_cmd="${GREP_CMD} /";; ipv4) ipv_match="^[0-9\./\-]+$";; ipv6) ipv_match="^[0-9a-fA-F:/\-]+$";; *) 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}" |\ ${final_cmd} |\ ${SORT_CMD} -u |\ while read do echo "${IPSET_ADD_OPTION} ${name} ${REPLY} ${opts}" done } 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 } ipset_list_active_names() { eval "${IPSET_CMD} ${IPSET_LIST_NAMES_EVAL}" } ipset_save_active_to_spool() { ${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 # 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 case "${cmd}" in create|-N|--create) local type="${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 opts="${*}" if [ "${opts/*prevent_reset_on_restart*/prevent_reset_on_restart}" = "prevent_reset_on_restart" ] then shift FIREHOL_IPSETS_KEEP[$name]=1 opts="${opts/prevent_reset_on_restart/}" fi echo "${IPSET_CREATE_OPTION} ${name} ${type} ${inet} ${opts}" >"${FIREHOL_DIR}/ipset.${name}.rules" echo "${IPSET_FLUSH_OPTION} ${name}" >>"${FIREHOL_DIR}/ipset.${name}.rules" FIREHOL_IPSETS_USED[$name]="CREATED" ;; 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_IPSETS_IPV[$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 } FIREHOL_IPSET_TMP_COUNTER=0 declare -A FIREHOL_IPSET_TMP_SETS=() ipset_to_temp_and_swap() { local name="${1}" # find a temporary name for the new ipset FIREHOL_IPSET_TMP_COUNTER=$[ FIREHOL_IPSET_TMP_COUNTER + 1 ] local tmpname="tmp-$$-${RANDOM}-${FIREHOL_IPSET_TMP_COUNTER}" FIREHOL_IPSET_TMP_SETS[$tmpname]=1 # give the temporary name to the set ${SED_CMD} -e "s|^\([^[:space:]]*\) ${name} \(.*\)|\1 ${tmpname} \2|g" \ -e "s|^\([^[:space:]]*\) ${name}$|\1 ${tmpname}|g" # swap them, to activate the temporary ipset echo "${IPSET_SWAP_OPTION} ${tmpname} ${name}" # destroy the temporary ipset echo "${IPSET_DESTROY_OPTION} ${tmpname}" } 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 } ipsets_apply() { local from="${1}" base="${FIREHOL_DIR}" restoring=0 x= # 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 # did we had an ipset helper for this ipset? [ ! -s "${base}/ipset.${x}.rules" ] && continue # shall we restore this ipset? if [ "${FIREHOL_IPSETS_USED[$x]}" = "EXISTS" ] then test $restoring -eq 1 && continue [ ${FIREHOL_IPSETS_RESPECT_KEEP} -eq 1 -a "${FIREHOL_IPSETS_KEEP[$x]}" = "1" ] && continue ipset_to_temp_and_swap "${x}" <"${base}/ipset.${x}.rules" >>"${FIREHOL_DIR}/ipsets.restore" else # it does not exist... # copy the generated rules to the ipset restoration file ${CAT_CMD} "${base}/ipset.${x}.rules" >>"${FIREHOL_DIR}/ipsets.restore" fi FIREHOL_IPSETS_USED[$x]="RESTORED" done if [ -s "${FIREHOL_DIR}/ipsets.restore" ] then ${IPSET_CMD} ${IPSET_RESTORE_OPTION} <"${FIREHOL_DIR}/ipsets.restore" if [ $? -ne 0 ] then error "${FUNCNAME}: Cannot apply generated ipset rules." # remove all temporary ipsets ipset_remove_all_tmp_sets return 1 else ipset_done_all_tmp_sets success fi if [ ! "${from}" = "spool" ] then # save the list and the new rules to our spool directory for x in ${!FIREHOL_IPSETS_USED[@]} do if [ "${FIREHOL_IPSETS_USED[$x]}" = "RESTORED" ] then cp -p "${base}/ipset.${x}.rules" "${FIREHOL_SPOOL_DIR}/ipset.${x}.rules" FIREHOL_IPSETS_USED[$x]="CREATED" elif [ "${FIREHOL_IPSETS_USED[$x]}" = "EXISTS" ] then FIREHOL_IPSETS_USED[$x]="CREATED" fi done declare -p FIREHOL_IPSETS_USED FIREHOL_IPSETS_IPV FIREHOL_IPSETS_KEEP >"${FIREHOL_SPOOL_DIR}/ipsets.conf" cp "${FIREHOL_DIR}/ipsets.restore" "${FIREHOL_SPOOL_DIR}/last.ipsets.restore" ipset_save_active_to_spool fi else success "sets already exist, not updated IPs" fi return 0 } # ------------------------------------------------------------------------------ # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # ------------------------------------------------------------------------------ # # INTERNAL FUNCTIONS BELLOW 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=ipv4 FIREHOL_NS_CURR=${FIREHOL_DEFAULT_NAMESPACE} FIREHOL_NS_STACK=() FIREHOL_NS_PREP= 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) interface() { work_realcmd_primary ${FUNCNAME} "${@}" # --- close any open command --- close_cmd || return 1 # --- reset namespace if [ "${FIREHOL_NS_PREP}" != "" ] then FIREHOL_NS_STACK=(${FIREHOL_NS_PREP}) FIREHOL_NS_CURR=${FIREHOL_NS_PREP} else FIREHOL_NS_STACK=(${FIREHOL_DEFAULT_NAMESPACE}) FIREHOL_NS_CURR=${FIREHOL_DEFAULT_NAMESPACE} fi FIREHOL_NS_PREP= # --- test prerequisites --- require_work clear || return 1 set_work_function -ne "Initializing ${FUNCNAME}" # --- get paramaters 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 "Initializing ${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 return 0 } interface4() { ipv4 interface "${@}" } interface6() { ipv6 interface "${@}" } interface46() { both interface "${@}" } router() { work_realcmd_primary ${FUNCNAME} "${@}" # --- close any open command --- close_cmd || return 1 # --- reset namespace if [ "${FIREHOL_NS_PREP}" != "" ] then FIREHOL_NS_STACK=(${FIREHOL_NS_PREP}) FIREHOL_NS_CURR=${FIREHOL_NS_PREP} else FIREHOL_NS_STACK=(${FIREHOL_DEFAULT_NAMESPACE}) FIREHOL_NS_CURR=${FIREHOL_DEFAULT_NAMESPACE} fi FIREHOL_NS_PREP= # --- test prerequisites --- require_work clear || return 1 set_work_function -ne "Initializing ${FUNCNAME}" # --- get paramaters 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 "Initializing ${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 FIREHOL_ROUTING=1 return 0 } router4() { ipv4 router "${@}" } router6() { ipv6 router "${@}" } router46() { both router "${@}" } 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 } # 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" shift [ "${cmd}" = "ip6tables" ] && n=table6 if [ "z${1}" = "z-t" ] then t="${2}" shift 2 fi 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|-I) echo "${*}" >>"${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 } 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} "${@}" [ $firewall_policy_applied -eq 0 ] && firewall_policy 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 } FIREHOL_COMMAND6_COUNTER=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} "${@}" [ $firewall_policy6_applied -eq 0 ] && firewall_policy6 if [ $FIREHOL_FAST_ACTIVATION -eq 1 ] then run_fast ip6tables "${@}" else postprocess -ns "${IP6TABLES_CMD}" "${@}" FIREHOL_COMMAND6_COUNTER=$[FIREHOL_COMMAND6_COUNTER + 1] fi return 0 } iptables_both() { if running_ipv4; then iptables "${@}" || return; fi if running_ipv6; then ip6tables "${@}" || return; fi return 0 } # ------------------------------------------------------------------------------ # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # ------------------------------------------------------------------------------ # # INTERNAL FUNCTIONS BELLOW 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 "Setting policy of ${work_name} to ${1}" work_policy="$*" return 0 } server() { work_realcmd_secondary ${FUNCNAME} "${@}" require_work set any || return 1 smart_function server "${@}" return $? } server4() { ipv4 server "${@}" } server6() { ipv6 server "${@}" } server46() { both server "${@}" } client() { work_realcmd_secondary ${FUNCNAME} "${@}" require_work set any || return 1 smart_function client "${@}" return $? } client4() { ipv4 client "${@}" } client6() { ipv6 client "${@}" } client46() { both client "${@}" } route() { work_realcmd_secondary ${FUNCNAME} "${@}" require_work set router || return 1 smart_function server "${@}" return $? } route4() { ipv4 route "${@}" } route6() { ipv6 route "${@}" } route46() { both route "${@}" } # --- protection --------------------------------------------------------------- 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="${2}" \ burst="${3}" test -z "${rate}" && rate="100/s" test -z "${burst}" && burst="50" set_work_function -ne "Generating protections on '${prface}' for ${work_cmd} '${work_name}'" for x in ${type} do case "${x}" in none|NONE) return 0 ;; bad-packets|BAD-PACKETS) protection ${reverse} "fragments new-tcp-w/o-syn malformed-xmas malformed-null malformed-bad invalid" "${rate}" "${burst}" return $? ;; strong|STRONG|full|FULL|all|ALL) protection ${reverse} "fragments new-tcp-w/o-syn icmp-floods syn-floods malformed-xmas malformed-null malformed-bad invalid" "${rate}" "${burst}" return $? ;; invalid|INVALID) if [ "${FIREHOL_DROP_INVALID}" -eq 0 ] then iptables_both -A "${in}_${work_name}" -m conntrack --ctstate INVALID -j DROP || return 1 fi ;; fragments|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 "Generating rules to be protected from packet fragments on '${prface}' for ${work_cmd} '${work_name}'" # # rule in chain "${mychain}" loglimit "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|NEW-TCP-W/O-SYN) local mychain="${pre}_${work_name}_nosyn" create_chain filter "${mychain}" "${in}_${work_name}" in proto tcp state NEW custom "! --syn" || return 1 set_work_function "Generating rules to be protected from new TCP connections without the SYN flag set on '${prface}' for ${work_cmd} '${work_name}'" rule in chain "${mychain}" loglimit "NEW TCP w/o SYN" action drop || return 1 ;; icmp-floods|ICMP-FLOODS) local mychain="${pre}_${work_name}_icmpflood" 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 "Generating rules to be protected from ICMP floods on '${prface}' for ${work_cmd} '${work_name}'" rule in chain "${mychain}" limit "${rate}" "${burst}" action return || return 1 rule in chain "${mychain}" loglimit "ICMP FLOOD" action drop || return 1 ;; syn-floods|SYN-FLOODS) local mychain="${pre}_${work_name}_synflood" create_chain filter "${mychain}" "${in}_${work_name}" in proto tcp custom "--syn" || return 1 set_work_function "Generating rules to be protected from TCP SYN floods on '${prface}' for ${work_cmd} '${work_name}'" rule in chain "${mychain}" limit "${rate}" "${burst}" action return || return 1 rule in chain "${mychain}" loglimit "SYN FLOOD" action drop || return 1 ;; all-floods|ALL-FLOODS) local mychain="${pre}_${work_name}_allflood" create_chain filter "${mychain}" "${in}_${work_name}" in state NEW || return 1 set_work_function "Generating rules to be protected from ALL floods on '${prface}' for ${work_cmd} '${work_name}'" rule in chain "${mychain}" limit "${rate}" "${burst}" action return || return 1 rule in chain "${mychain}" loglimit "ALL FLOOD" action drop || return 1 ;; malformed-xmas|MALFORMED-XMAS) local mychain="${pre}_${work_name}_malxmas" create_chain filter "${mychain}" "${in}_${work_name}" in proto tcp custom "--tcp-flags ALL ALL" || return 1 set_work_function "Generating rules to be protected from packets with all TCP flags set on '${prface}' for ${work_cmd} '${work_name}'" rule in chain "${mychain}" loglimit "MALFORMED XMAS" action drop || return 1 ;; malformed-null|MALFORMED-NULL) local mychain="${pre}_${work_name}_malnull" create_chain filter "${mychain}" "${in}_${work_name}" in proto tcp custom "--tcp-flags ALL NONE" || return 1 set_work_function "Generating rules to be protected from packets with all TCP flags unset on '${prface}' for ${work_cmd} '${work_name}'" rule in chain "${mychain}" loglimit "MALFORMED NULL" action drop || return 1 ;; malformed-bad|MALFORMED-BAD) local mychain="${pre}_${work_name}_malbad" create_chain filter "${mychain}" "${in}_${work_name}" in proto tcp custom "--tcp-flags SYN,FIN SYN,FIN" || return 1 set_work_function "Generating rules to be protected from packets with illegal TCP flags on '${prface}' for ${work_cmd} '${work_name}'" rule in chain "${in}_${work_name}" action "${mychain}" proto tcp custom "--tcp-flags SYN,RST SYN,RST" || return 1 rule in chain "${in}_${work_name}" action "${mychain}" proto tcp custom "--tcp-flags ALL SYN,RST,ACK,FIN,URG" || return 1 rule in chain "${in}_${work_name}" action "${mychain}" proto tcp custom "--tcp-flags ALL FIN,URG,PSH" || return 1 rule in chain "${mychain}" loglimit "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" 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 2 fi fi # activation-phase command to check for the existance 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 existance 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}" modprobe_cmd ${mod} -q if [ $? -gt 0 ] then check_kernel_module ${mod} || runtime_error warn 1 "$(config_line)" "${MODPROBE_CMD}" ${mod} -q 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 BELLOW 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 occures 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}'" # accepting RELATED 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 ipv4 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 pop_flow_inheritance # make sure we have a policy test -z "${work_policy}" && work_policy="${DEFAULT_INTERFACE_POLICY}" case "${work_policy}" in return|RETURN) set_work_function "Nothing to be done for policy RETURN of interface '${work_name}'" return 0 ;; accept|ACCEPT) ;; *) local -a inlog=(loglimit "IN-${work_name}") local -a outlog=(loglimit "OUT-${work_name}") ;; esac 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 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}'" 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 ipv4 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 pop_flow_inheritance # make sure we have a policy test -z "${work_policy}" && work_policy="${DEFAULT_ROUTER_POLICY}" case "${work_policy}" in return|RETURN) set_work_function "Nothing to be done for policy RETURN of router '${work_name}'" return 0 ;; accept|ACCEPT) ;; *) local -a inlog=(loglimit "PASS-${work_name}") local -a outlog=(loglimit "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 return 0 } # ------------------------------------------------------------------------------ # close_master # WHY: # Finalizes the rules for the whole firewall. # It assummes there is not primary command open. close_master() { set_work_function "Finilizing firewall policies" FIREHOL_FILTERING_STARTED=0 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 "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 "Setting default unmatched policy (options: UNMATCHED_INPUT_POLICY UNMATCHED_OUTPUT_POLICY UNMATCHED_ROUTER_POLICY)" rule chain INPUT loglimit "IN-unknown" action ${UNMATCHED_INPUT_POLICY} || return 1 rule chain OUTPUT loglimit "OUT-unknown" action ${UNMATCHED_OUTPUT_POLICY} || return 1 rule chain FORWARD loglimit "PASS-unknown" action ${UNMATCHED_ROUTER_POLICY} || return 1 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=() group() { work_realcmd_primary ${FUNCNAME} "${@}" require_work set any || return 1 local type="${1}"; shift case $type in with|start|begin) push_namespace "${FIREHOL_NS_CURR}" # increase the counter FIREHOL_GROUP_COUNTER=$[FIREHOL_GROUP_COUNTER + 1] set_work_function "Starting 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 } group4() { ipv4 group "${@}" } group6() { ipv6 group "${@}" } group46() { both group "${@}" } close_all_groups() { while [ ${FIREHOL_GROUP_DEPTH} -gt 0 ] do group close || return 1 done 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 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}" \ count=0 val= shift 5 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 seperator, 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 # Do the rule case "${action}" in NONE) return 0 ;; ACCEPT) # do we have any options for this accept? if [ ! -z "${action_param[0]}" ] then # find the options we have case "${action_param[0]}" in "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 "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 -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 " /." "___"`" # 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 "${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 ACCEPT # accept NEW connections within the given limits. $iptables_cmd -t ${table} -A "${accept_limit_chain}" -m limit --limit "${freq}" --limit-burst "${burst}" -j ACCEPT # log the overflow NEW connections reaching this step within the new chain local -a logopts_arg=() if [ "${FIREHOL_LOG_MODE}" = "ULOG" ] then logopts_arg=("--ulog-prefix=${FIREHOL_LOG_ESCAPE}${FIREHOL_LOG_PREFIX}LIMIT_OVERFLOW:${FIREHOL_LOG_ESCAPE}") elif [ "${FIREHOL_LOG_MODE}" = "NFLOG" ] then logopts_arg=("--nflog-prefix=${FIREHOL_LOG_ESCAPE}${FIREHOL_LOG_PREFIX}LIMIT_OVERFLOW:${FIREHOL_LOG_ESCAPE}") else logopts_arg=("--log-level" "${FIREHOL_LOG_LEVEL}" "--log-prefix=${FIREHOL_LOG_ESCAPE}${FIREHOL_LOG_PREFIX}LIMIT_OVERFLOW:${FIREHOL_LOG_ESCAPE}") fi $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} "${logopts_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 -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 " /." "___"`" # 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 "${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 ACCEPT # 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 ACCEPT 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 "${FIREHOL_CHAINS_DIR}/${name}.${iptables_cmd}" $iptables_cmd -A "${name}" -m conntrack --ctstate ESTABLISHED -j ACCEPT # 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} ;; *) error "Internal error. Cannot understand action ${action} with parameter '${action_param[0]}'." return 1 ;; esac fi ;; 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 $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 } 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= \ action= state= statenot= \ failed=0 reverse=0 \ swi=0 swo=0 \ custom= \ accounting= \ ipsetnot= ipsetname= ipsetflags= ipsetopts= \ inout= x= param= not= helper=() helpernot= # 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 reverse) reverse=1 ;; nolog) nolog=1 ;; noowner) noowner=1 ;; softwarnings) softwarnings=1 ;; nosoftwarnings) softwarnings=0 ;; set_work_inface) swi=1 ;; set_work_outface) swo=1 ;; return_if_not_matched) return_if_not_matched=1 ;; optimal) optimal=1 ;; accurate) optimal=0 ;; push_flow_inheritance) push_flow_inheritance_type="${1}"; shift ;; insert_as) positive_rule_number="${1}"; shift ;; in) # this is incoming traffic - ignore packet ownership inout="in" noowner=1 nomirror=0 nomac=0 ;; out) # this is outgoing traffic - ignore packet ownership if not in an interface 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 '${inface[*]}' 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) local want= if [ \( "${param//src*/src}" = "src" -a ${reverse} -eq 0 \) -o \( "${param/dst*/dst}" = "dst" -a ${reverse} -eq 1 \) ] then if [ "${param}" = "src4" -o "${param}" = "dst4" ] then want="src4" elif [ "${param}" = "src6" -o "${param}" = "dst6" ] then want="src6" else running_ipv4 && want="src4" running_ipv6 && want="src6" fi if [ "${want}" = "src4" ] 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 else 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 [ "${param}" = "src4" -o "${param}" = "dst4" ] then want="dst4" elif [ "${param}" = "src6" -o "${param}" = "dst6" ] then want="dst6" else running_ipv4 && want="dst4" running_ipv6 && want="dst6" fi if [ "${want}" = "dst4" ] 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 else 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 ;; 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 ;; 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="${1}" connlimit_mask="${2}" shift 2 ;; acct|accounting) test ${softwarnings} -eq 1 -a ! -z "${not}" && softwarning "Cannot negate an accounting. 'not' ignored." if [ ${ENABLE_ACCOUNTING} -eq 1 ] then accounting="$2" 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" ;; 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" 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 ;; ACCEPT) action="ACCEPT" if [ "${1}" = "with" ] then shift case "${1}" in 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 ;; REJECT) action="REJECT" if [ "${1}" = "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" 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) [[ "${2}" =~ ":" ]] && [ "${proto}" = "any" ] && proto="tcp udp" 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) [[ "${2}" =~ ":" ]] && [ "${proto}" = "any" ] && proto="tcp udp" 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" 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" ] then action_param=("--tproxy-mark" "${2}") shift 2 fi if [ "${1}" = "on-port" -o "${1}" = "to-port" -o "${1}" = "to" ] 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}" = "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" ] 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" ] 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) action_param=("--set-mark" "${2}") shift 2 ;; save) if [ "${2}" = "mask" ] then action_param=("--save-mark" "--mask" "${3}") shift 3 else action_param=("--save-mark") shift 1 fi ;; restore) if [ "${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" "${2}") shift else action_param=("--set-dscp" "${2}") fi 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 ;; 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 ;; *) 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 normaly though if a helper is required. if [ ${optimal} -eq 1 -a "${table}" = "filter" -a -z "${helper[*]}" ] 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 the user did not specified 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 # log mode selection if [ "${FIREHOL_LOG_MODE}" = "ULOG" ] then logopts_arg=("--ulog-prefix=${FIREHOL_LOG_ESCAPE}${FIREHOL_LOG_PREFIX}${logtxt}:${FIREHOL_LOG_ESCAPE}") elif [ "${FIREHOL_LOG_MODE}" = "NFLOG" ] then logopts_arg=("--nflog-prefix=${FIREHOL_LOG_ESCAPE}${FIREHOL_LOG_PREFIX}${logtxt}:${FIREHOL_LOG_ESCAPE}") else logopts_arg=("--log-level" "${loglevel}" "--log-prefix=${FIREHOL_LOG_ESCAPE}${FIREHOL_LOG_PREFIX}${logtxt}:${FIREHOL_LOG_ESCAPE}") fi # log / loglimit case "${log}" in '') logrule=none ;; limit) logrule=limit ;; normal) logrule=normal ;; *) 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 # * 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 [ ! -z "${connlimit}" ] && protected_args=("${protected_args[@]}" "-m" "connlimit" "--connlimit-above" "${connlimit}" "--connlimit-mask" "${connlimit_mask}") # 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 # if there only source or destination ports, we can use multiport if [ "${sport[*]}" = "any" -a ! "${dport[*]}" = "any" -a "${#dport[*]}" -gt 1 ] then dp="${dport[*]}" positive_args=("${positive_args[@]}" "-m" "multiport" ${dportnot} "--destination-ports" "${dp// /,}") dportnot= dport=(any) sp= elif [ "${dport[*]}" = "any" -a ! "${sport[*]}" = "any" -a "${#sport[*]}" -gt 1 ] then sp="${sport[*]}" positive_args=("${positive_args[@]}" "-m" "multiport" ${sportnot} "--source-ports" "${sp// /,}") sportnot= sport=(any) dp= elif [ "${dport[*]}" = "${sport[*]}" -a ! "${sport[*]}" = "any" -a "${sportnot}" = "${dportnot}" -a "${#sport[*]}" -gt 1 ] then sp="${sport[*]}" positive_args=("${positive_args[@]}" "-m" "multiport" ${sportnot} "--ports" "${sp// /,}") sportnot= sport=(any) dportnot= dport=(any) sp= dp= fi 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 intermidiate chain to store the negative expression, # and change the action of the rule to point to this action. # In this case, bellow 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 banch 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 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 ;; *) ${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 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 ;; *) ${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 sp in ${sport[*]} do for pr in ${proto[*]} do iptables_both -t ${table} -A "${negative_chain}" -p "${pr}" ${not} --sport "${sp}" -j RETURN 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 dp in ${dport[*]} do for pr in ${proto[*]} do iptables_both -t ${table} -A "${negative_chain}" -p "${pr}" ${not} --dport "${dp}" -j RETURN 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. # ipvall 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 [ "$logrule" = "limit" ] && ( ${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] ) [ "$logrule" = "normal" ] && ( ${iptables} -t ${table} -A "${negative_chain}" -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}" || failed=$[failed + 1] ) [ ! -z "${accounting}" ] && ( ${iptables} -t ${table} -A "${negative_chain}" -m nfacct --nfacct-name "${accounting}" || failed=$[failed + 1] ) 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}" "${action_param[@]}" -- -t ${table} -A "${negative_chain}" ${pr} || 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) # 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 banch 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 [ "$logrule" = "limit" ] && ( ${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] ) [ "$logrule" = "normal" ] && ( ${iptables} -t ${table} -A "${logging_chain}" -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}" || failed=$[failed + 1] ) [ ! -z "${accounting}" ] && ( ${iptables} -t ${table} -A "${logging_chain}" -m nfacct --nfacct-name "${accounting}" || failed=$[failed + 1] ) 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}" "${action_param[@]}" -- -t ${table} -A "${logging_chain}" ${pr} || 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) # 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 banch 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}" "${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 contrained parameters # so that the positive rules do not add them again protected_args=() # 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 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}") # 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=(${ownernot} "--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=(${ownernot} "--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 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}) ;; *) 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 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}) ;; *) d_arg=(${dstnot} "-d" "${d}") ;; esac # sport for sp in ${sport[*]} do case ${sp} in any|ANY) sp_arg=() ;; *) sp_arg=(${sportnot} "--sport" "${sp}") ;; esac # dport for dp in ${dport[*]} do case ${dp} in any|ANY) dp_arg=() ;; *) dp_arg=(${dportnot} "--dport" "${dp}") ;; esac # 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 [ -z "${positive_rule_number}" ] then [ "$logrule" = "limit" ] && ( ${iptables} -t ${table} -A "${chain}" "${basecmd[@]}" -m limit --limit "${FIREHOL_LOG_FREQUENCY}" --limit-burst "${FIREHOL_LOG_BURST}" -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}" || failed=$[failed + 1] ) [ "$logrule" = "normal" ] && ( ${iptables} -t ${table} -A "${chain}" "${basecmd[@]}" -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}" || failed=$[failed + 1] ) [ ! -z "${accounting}" ] && ( ${iptables} -t ${table} -A "${chain}" "${basecmd[@]}" -m nfacct --nfacct-name "${accounting}" || failed=$[failed + 1] ) fi # do it! if [ -z "${positive_rule_number}" ] then x="-A ${chain}" else x="-I ${chain} ${positive_rule_number}" fi rule_action_param ${iptables} "${action}" "${statenot}" "${state}" "${table}" "${action_param[@]}" -- -t ${table} ${x} "${basecmd[@]}" || failed=$[failed + 1] 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}: " "${@}" 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 successfull 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 apropriately. 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 unecessary 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. smart_function() { local type="${1}" services=(${2//,/ }) service= servname= suffix= mychain= ret= fn= dogroup=0 reverse= # 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}" 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 && 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 "Running complex rules function ${fn}() for ${type} '${service}'" "${fn}" "${mychain}" "${type}" "${@}" "${FIREHOL_RULESET_MODE}" ret=$? test $ret -eq 0 && 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 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 "Running 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 ${FIREHOL_ENABLE_SPINNER} -eq 1 && spinner ${FIREHOL_COMMAND_COUNTER} FIREHOL_FILTERING_STARTED=1 config_line -ne work_realcmd=("${@}") test ${FIREHOL_CONF_SHOW} -eq 1 && show_work_realcmd 1 } work_realcmd_secondary() { 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 } work_realcmd_helper() { 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 } wait_for_interface() { local iface="${1}" timeout=60 found=0 start=`date +%s` addr= shift [ -n "$1" ] && timeout="${1}" while [ "`date +%s`" -lt $(($start+$timeout)) -a $found -eq 0 ] do addr=`ip addr show $iface 2> /dev/null | awk '$1 ~ /^inet$/ {print $2}'` [ -n "$addr" ] && found=1 [ $found -eq 0 ] && sleep 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/--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 "${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 "${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 "${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 test ! -z "`${FIND_CMD} "${FIREHOL_CONFIG_DIR}" -newer "${FIREHOL_LAST_SUCCESSFUL_COMMAND}"`" \ && warning "${FIREHOL_CONFIG_DIR} has updated files. Cannot restore saved firewall." \ && return 4 test ! -z "`${FIND_CMD} "${FIREHOL_SERVICES_DIR}" -newer "${FIREHOL_LAST_SUCCESSFUL_COMMAND}"`" \ && warning "${FIREHOL_SERVICES_DIR} has updated files. Cannot restore saved firewall." \ && return 5 if [ ${ENABLE_IPSET} -eq 1 ] then ipsets_apply 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 -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." firewall_policy_applied=1 firewall_policy6_applied=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" 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) lnstat -c -1 -f nf_conntrack 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 -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" pager_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 progress "Updating ipset '${name}' with options: ${*}" found=0 for x in $( ipset_list_active_names ) do [ "$x" = "${name}" ] && found=1 && break done if [ $found -eq 1 ] then tmp=$(${MKTEMP_CMD} "${FIREHOL_DIR}/ipset-XXXXXXXXXX") || exit 1 # save the current set ${IPSET_CMD} ${IPSET_SAVE_OPTION} ${name} >${tmp}.old # find the create statement ${EGREP_CMD} "^(${IPSET_CREATE_OPTION}|create|-N|--create) " <${tmp}.old >${tmp} if [ ! -s ${tmp} -a -f "${FIREHOL_SPOOL_DIR}/ipset.${name}.rules" ] then # try the rules we generated previously ${GREP_CMD} "^${IPSET_CREATE_OPTION} ${name} " \ <"${FIREHOL_SPOOL_DIR}/ipset.${name}.rules" \ >${tmp} fi # do we have a 'create' statement? if [ ! -s ${tmp} ] then failure "cannot find ipset create statement" # "Updating ipset '${name}' with options: ${*}" echo >&2 "Sorry. Cannot find the template to re-create ipset '${name}'." echo >&2 "Make sure the firehol-defaults.conf reflects your ipset create statement." exit 1 fi # ok, we have the ipset 'create' statement in $tmp # add a flush to it echo "${IPSET_FLUSH_OPTION} ${name}" >>${tmp} # 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_to_temp_and_swap "${name}" <${tmp} >"${FIREHOL_DIR}/ipsets.restore" # and activate it ${IPSET_CMD} ${IPSET_RESTORE_OPTION} <"${FIREHOL_DIR}/ipsets.restore" 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 "$(( $(cat ${tmp} | wc -l) - 2 )) IPs" # "Updating ipset '${name}' with options: ${*}" # save the new ipset ${IPSET_CMD} ${IPSET_SAVE_OPTION} ${name} >${tmp}.new # sort the two sets # we use the files saved from ipset, so that if the kernel is # making something magic to them (it does not currently), # we will compare what the kernel actually sees. ${SORT_CMD} <${tmp}.new >${tmp}.new.sorted ${SORT_CMD} <${tmp}.old >${tmp}.old.sorted diff ${tmp}.old.sorted ${tmp}.new.sorted |\ ${SED_CMD} -e "s|[\t ]\+| |g" -e "s|^ \+||g" -e "s| \+$||g" \ -e "s|^\([<>]\) [^[:space:]]* ${name} \([0-9a-fA-F/\.:-]\+\) .*$|\1 \2|g" \ -e "s|^\([<>]\) [^[:space:]]* ${name} \([0-9a-fA-F/\.:-]\+\)$|\1 \2|g" |\ ${EGREP_CMD} "^[<>] [0-9a-fA-F/\.:-]+$" >${tmp}.diff echo if [ ! -s ${tmp}.diff ] then echo " >> No differences - the new set is exactly the same with the old." else cat ${tmp}.diff | ${GREP_CMD} "^< " | ${SED_CMD} "s|^< ||g" | ${TR_CMD} "\n" " " >${tmp}.removed cat ${tmp}.diff | ${GREP_CMD} "^> " | ${SED_CMD} "s|^> ||g" | ${TR_CMD} "\n" " " >${tmp}.added if [ -s ${tmp}.removed ] then echo -n " < Removed: " cat ${tmp}.removed echo fi if [ -s ${tmp}.added ] then echo -n " > Added : " cat ${tmp}.added echo fi fi echo # keep it for restoration if [ -f "${FIREHOL_SPOOL_DIR}/ipset.${name}.rules" ] then progress "Keeping ipset '${name}' for later restoration" cp ${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 else failed "no collection with name '${name}'" # "Updating ipset '${name}' with options: ${*}" fi exit 1 ;; *) 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 ;; --) FIREHOL_TRY=1 ;; *) 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} < 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} </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 [ ${FIREHOL_FAST_ACTIVATION} -eq 0 ] && FIREHOL_LOG_ESCAPE= # ------------------------------------------------------------------------------ # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # ------------------------------------------------------------------------------ # # MAIN PROCESSING - Interactive mode # # ------------------------------------------------------------------------------ # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # ------------------------------------------------------------------------------ if [ "${FIREHOL_MODE}" = "EXPLAIN" ] then 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} < 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 require_cmd ip require_cmd ss require_cmd date require_cmd hostname 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 <&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} < 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-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 ${FIREHOL_SAVED}.new ${FIREHOL_SAVED} test -f ${FIREHOL_SAVED6}.new && mv ${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= initialize_firewall() { 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 test ${FIREHOL_ROUTING} -eq 1 && postprocess -warn ${SYSCTL_CMD} -w "net.ipv4.ip_forward=1" if [ "${FIREHOL_CONNTRACK_HELPERS_ASSIGNMENT}" = "kernel" ] then postprocess -warn ${SYSCTL_CMD} -w "net.netfilter.nf_conntrack_helper=1" else postprocess -warn ${SYSCTL_CMD} -w "net.netfilter.nf_conntrack_helper=0" fi test ! -z "${FIREHOL_CONNTRACK_LOOSE_MATCHING}" && postprocess -warn ${SYSCTL_CMD} -e "net.netfilter.nf_conntrack_tcp_loose=${FIREHOL_CONNTRACK_LOOSE_MATCHING}" test ! -z "${FIREHOL_TCP_SYN_COOKIES}" && postprocess -warn ${SYSCTL_CMD} -e "net.ipv4.tcp_syncookies=${FIREHOL_TCP_SYN_COOKIES}" test ! -z "${FIREHOL_TCP_TIMESTAMPS}" && postprocess -warn ${SYSCTL_CMD} -e "net.ipv4.tcp_timestamps=${FIREHOL_TCP_TIMESTAMPS}" test ! -z "${FIREHOL_CONNTRACK_MAX}" && postprocess -warn ${SYSCTL_CMD} -e "net.netfilter.nf_conntrack_max=${FIREHOL_CONNTRACK_MAX}" test ! -z "${FIREHOL_CONNTRACK_HASHSIZE}" && postprocess -warn ${SYSCTL_CMD} -e "echo ${FIREHOL_CONNTRACK_HASHSIZE} >/sys/module/nf_conntrack/parameters/hashsize" 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}" 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_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_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" ] then set_work_function "Trusting ${iptables_cmd} lo (option: FIREHOL_TRUST_LOOPBACK)" ${iptables_cmd} -t filter -A INPUT -i lo -j ACCEPT ${iptables_cmd} -t filter -A OUTPUT -o lo -j ACCEPT 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 # 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 # 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 "Droping ${iptables_cmd} connection tracker INVALID packets (option: FIREHOL_DROP_INVALID)" ${iptables_cmd} -t filter -A INPUT -m conntrack --ctstate INVALID -j DROP ${iptables_cmd} -t filter -A OUTPUT -m conntrack --ctstate INVALID -j DROP ${iptables_cmd} -t filter -A FORWARD -m conntrack --ctstate INVALID -j DROP fi if [ "${FIREHOL_DROP_ORPHAN_TCP_ACK_FIN}" = "1" ] then set_work_function "Silently droping TCP ACK+FIN packets (option: FIREHOL_DROP_ORPHAN_TCP_ACK_FIN)" # Silently drop orphan TCP/ACK FIN packets ${iptables_cmd} -t filter -A INPUT -p tcp --tcp-flags ALL ACK,FIN -m conntrack --ctstate NEW -j DROP ${iptables_cmd} -t filter -A OUTPUT -p tcp --tcp-flags ALL ACK,FIN -m conntrack --ctstate NEW -j DROP ${iptables_cmd} -t filter -A FORWARD -p tcp --tcp-flags ALL ACK,FIN -m conntrack --ctstate NEW -j DROP fi if [ ! -z ${FIREHOL_GLOBAL_RPFILTER} ] then ${iptables_cmd} -t raw -A PREROUTING -m rpfilter ${FIREHOL_GLOBAL_RPFILTER} -j DROP fi } firewall_policy_applied=0 firewall_policy() { test ${firewall_policy_applied} -eq 1 && return 0 firewall_policy_applied=1 firewall_policy_common iptables } firewall_policy6_applied=0 firewall_policy6() { test ${firewall_policy6_applied} -eq 1 && return 0 firewall_policy6_applied=1 firewall_policy_common ip6tables } # 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 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}" enable trap # Enable the trap buildin shell command. enable exit # Enable the exit buildin shell command. close_cmd || ret=$[ret + 1] close_master || ret=$[ret + 1] 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 # "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 splitted ones. cd $FIREHOL_DIR/fast/tables || exit 1 for firehol_table in `ls` do ( echo "*${firehol_table}" test -f $FIREHOL_DIR/fast/table.${firehol_table}.policy && ${CAT_CMD} $FIREHOL_DIR/fast/table.${firehol_table}.policy test -f $FIREHOL_DIR/fast/table.${firehol_table}.chains && ${CAT_CMD} $FIREHOL_DIR/fast/table.${firehol_table}.chains test -f $FIREHOL_DIR/fast/table.${firehol_table}.rules && ${CAT_CMD} $FIREHOL_DIR/fast/table.${firehol_table}.rules echo "COMMIT" ) >>${FIREHOL_OUTPUT}.fast done cd $FIREHOL_DIR/fast/table6s || exit 1 for firehol_table in `ls` do ( echo "*${firehol_table}" test -f $FIREHOL_DIR/fast/table6.${firehol_table}.policy && ${CAT_CMD} $FIREHOL_DIR/fast/table6.${firehol_table}.policy test -f $FIREHOL_DIR/fast/table6.${firehol_table}.chains && ${CAT_CMD} $FIREHOL_DIR/fast/table6.${firehol_table}.chains test -f $FIREHOL_DIR/fast/table6.${firehol_table}.rules && ${CAT_CMD} $FIREHOL_DIR/fast/table6.${firehol_table}.rules echo "COMMIT" ) >>${FIREHOL_OUTPUT}.fast6 done if [ "${FIREHOL_MODE}" = "DEBUG" ] then test $ENABLE_IPV4 -eq 1 && ${CAT_CMD} ${FIREHOL_OUTPUT}.fast test $ENABLE_IPV6 -eq 1 && ${CAT_CMD} ${FIREHOL_OUTPUT}.fast6 exit 1 fi # apply the new ipsets if [ ${ENABLE_IPSET} -eq 1 ] then ipsets_apply || exit 1 fi progress "Fast activating new firewall" initialize_firewall # 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 "${FIREHOL_OUTPUT}.log" | grep "Error occurred at line: " | cut -d ':' -f 2` test -z "$line" && line=`cat "${FIREHOL_OUTPUT}.log" | grep "iptables-restore: line " | grep failed | cut -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 "${FIREHOL_OUTPUT}.log" | grep "Error occurred at line: " | cut -d ':' -f 2` test -z "$line" && line=`cat "${FIREHOL_OUTPUT}.log" | grep "ip6tables-restore: line " | grep failed | cut -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_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 || 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)" initialize_firewall file close 21 source "${FIREHOL_OUTPUT}" "${@}" if [ $? -ne 0 ] then work_runtime_error=$[work_runtime_error+1] else finalize_firewall fi fi if [ ${work_runtime_error} -gt 0 ] then failure # "Activating new firewall" syslog err "Activation of new firewall failed." # 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 " "${FIREHOL_ORIGINAL_ARGS[@]}" printf >&2 "\n" fi exit 1 fi success # "Activating new firewall (${FIREHOL_COMMAND_COUNTER} rules)" 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 "Successfull 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 "${FIREHOL_DIR}/firewall_restore_commands.sh" "${FIREHOL_SPOOL_DIR}/firewall_restore_commands.sh" chown root:root "${FIREHOL_SPOOL_DIR}/firewall_restore_commands.sh" chmod 700 "${FIREHOL_SPOOL_DIR}/firewall_restore_commands.sh" # keep track if we do ipv4 if [ $ENABLE_IPV4 -eq 1 ] then touch "${FIREHOL_SPOOL_DIR}/ipv4.enable" chown root:root "${FIREHOL_SPOOL_DIR}/ipv4.enable" chmod 600 "${FIREHOL_SPOOL_DIR}/ipv4.enable" else test -f "${FIREHOL_SPOOL_DIR}/ipv4.enable" && rm "${FIREHOL_SPOOL_DIR}/ipv4.enable" fi # keep track if we do ipv6 if [ $ENABLE_IPV6 -eq 1 ] then touch "${FIREHOL_SPOOL_DIR}/ipv6.enable" chown root:root "${FIREHOL_SPOOL_DIR}/ipv6.enable" chmod 600 "${FIREHOL_SPOOL_DIR}/ipv6.enable" else test -f "${FIREHOL_SPOOL_DIR}/ipv6.enable" && rm "${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 "${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 "${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