#!/bin/bash # # FireHOL - A firewall for humans... # # Copyright # # Copyright (C) 2003-2014 Costa Tsaousis # Copyright (C) 2012-2014 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 "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 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 # ------------------------------------------------------------------------------ # 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() { # echo >&2 "${FUNCNAME} ${*}" 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 save|restore|permanent) saverestore=1 ;; nosave|norestore|temp|temporary) saverestore=0 ;; stateless) stateful=0 ;; stateful) stateful=1 ;; *) echo >&2 "${FUNCNAME}: Unknown keyword '${1}'." exit 1 ;; esac shift done if [ ! -z "${MARKS_MASKS[$name]}" ] then echo >&2 "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 "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 "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 "INTERNAL ERROR: Cannot figure out the bits set of value $max." exit 1 fi if [ $[ bits + MARKS_TOTAL_BITS ] -gt 32 ] then echo >&2 "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 # echo "Mark $name with $[MARKS_MAX[$name] + 1] possible values (from 0 to ${MARKS_MAX[$name]}), uses ${MARKS_BITS[$name]} bits, has mask ${MARKS_MASKS[$name]} and values should be shifted by ${MARKS_SHIFT[$name]} bits" # declare -p MARKS_BITS MARKS_MASKS MARKS_MAX MARKS_SHIFT MARKS_STATEFUL MARKS_SAVERESTORE MARKS_SAVERESTORE_STATEFUL_MASK MARKS_SAVERESTORE_STATELESS_MASK >&2 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" # 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}" # 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 rooting of packets in the kernel # It will be enabled automatically if routers are defined in the firewall # Default: 0 FIREHOL_ROUTING="${FIREHOL_ROUTING-0}" # 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 output firewall. # They can also be set in the firewall config file. # 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 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 DEFAULT_ROUTER_POLICY="RETURN" # Should we drop all INVALID packets always? # INVALID packets as seen by the connection tracker. # Default: 0 FIREHOL_DROP_INVALID=0 # 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" # 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 # ---------------------------------------------------------------------- # 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 # 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 # 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="" # 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" # 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 # ---------------------------------------------------------------------- # 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 "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 "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" # ------------------------------------------------------------------------------ # 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 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 " IMPORTANT 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 " IMPORTANT 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 " IMPORTANT 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 "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 "Cannot create temporary directory." 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 } firehol_exit() { local restored="NO" if [ \( -f "${FIREHOL_SAVED}" -o -f "${FIREHOL_SAVED6}" \) -a "${FIREHOL_MODE}" = "START" ] then echo echo -n $"FireHOL: 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 $"FireHOL: Restoring old firewall:" else restored="FAILED" failure $"FireHOL: Restoring old firewall:" fi echo fi # remove the temporary directory created for this session if [ ${FIREHOL_ACTIVATED_SUCCESSFULLY} -eq 0 -a ${FIREHOL_CLEAN_TMP} -eq 0 ] then echo "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 # echo "$[ x << ${MARKS_SHIFT[$name]} ]/${MARKS_MASKS[$name]}" 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. 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} for (( i = 2; i < $all; i++ )) do [ ! "${BASH_SOURCE[$i]}" = "${PROGRAM_FILE}" ] && break done LAST_CONFIG_LINE="${BASH_LINENO[$[i-1]]}@${BASH_SOURCE[$i]}: ${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 } 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="tcp/1720" client_h323_ports="default" helper_h323="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" # Portmap clients appear to use ports bellow 1024 server_portmap_ports="udp/111 tcp/111" client_portmap_ports="any" 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="udp/5060" client_sip_ports="5060 default" helper_sip="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="syslog default" server_telnet_ports="tcp/23" client_telnet_ports="default" server_tftp_ports="udp/69" client_tftp_ports="default" helper_tftp="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 [ "${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= 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 # ---------------------------------------------------------------------- local x= sp= proto= sport= cp= cport= oIFS="${IFS}" local -a args=() for sp in ${my_server_ports} do IFS="/" args=( $sp ) IFS="${oIFS}" proto=${args[0]} sport=${args[1]} for cp in ${my_client_ports} do cport="${cp}" [ "${cport}" = "default" ] && cport="${client_ports}" set_work_function "Rules for ${server} ${type}, with server port(s) '${sp}' and client port(s) '${cp}'" # 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 done done for x in ${helpers} do set_work_function "Rules for ${server} ${type}, with helper '${x}'" rule ${in} action "$@" chain "${in}_${mychain}" custom "-m helper --helper ${x}" state ESTABLISHED,RELATED || return 1 rule ${out} reverse action "$@" chain "${out}_${mychain}" custom "-m helper --helper ${x}" state ESTABLISHED,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 " >>> 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 " >>> Ignoring service in '${FIREHOL_SERVICES_DIR}/${f}' due to malformed header." elif [ ${n} -ne ${FIREHOL_SERVICES_API} ] then echo >&2 " >>> 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 " >>> Ignoring service in '${FIREHOL_SERVICES_DIR}/${f}' due to malformed API minor number." else source ${f} ret=$? if [ ${ret} -ne 0 ] then echo >&2 " >>> 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 # ------------------------------------------------------------------------------ ecn_shame() { work_realcmd_helper $FUNCNAME "$@" softwarning "ECN_SHAME IP list no longer available, helper is ignored." return 0 } # define custom actions 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 ) while [ ! -z "${1}" ] do local what="${1}"; shift case "${what}" in chain) local name="${1}"; shift local act="${1}"; shift if [ -z "${name}" ] then error "Cannot create an action chain without a name." return 1 fi if [ -z "${act}" ] then error "Cannot create the action chain(s) '$name' without a default action." return 1 fi local nm= for nm in $name do create_chain filter ${nm} rule table filter chain ${nm} action "${act}" done ;; *) error "Cannot understand $FUNCNAME '${what}'." return 1 ;; esac done return 0 } masquerade() { work_realcmd_helper ${FUNCNAME} "$@" set_work_function -ne "Initializing $FUNCNAME" local f="${work_outface}" test "${1}" = "reverse" && f="${work_inface}" && shift test -z "${f}" && f="${1}" && shift 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 || return 1 FIREHOL_NAT=1 FIREHOL_ROUTING=1 return 0 } transparent_proxy_count=0 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 dport "${ports}" action REDIRECT to-port ${redirect} || return 1 rule table nat chain "in_trproxy.${transparent_proxy_count}" proto tcp 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 dport "${ports}" action REDIRECT to-port ${redirect} || return 1 rule table nat chain "out_trproxy.${transparent_proxy_count}" proto tcp action REDIRECT to-port ${redirect} || return 1 fi FIREHOL_NAT=1 FIREHOL_ROUTING=1 return 0 } 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 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}" \ action= shift 2 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 action=snat ;; to-destination) create_chain nat "nat.${nat_count}" PREROUTING noowner nolog "$@" outface any || return 1 action=dnat ;; redirect-to) create_chain nat "nat.${nat_count}" PREROUTING noowner nolog "$@" outface any || return 1 action=redirect ;; *) 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 "$@" action "${action}" to "${to}" nosoftwarnings src any dst any inface any outface any sport any dport any || return 1 FIREHOL_NAT=1 FIREHOL_ROUTING=1 return 0 } nat() { work_realcmd_helper $FUNCNAME "$@" set_work_function -ne "Initializing $FUNCNAME" nat_helper "$@" } 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}" "$@" } 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}" "$@" } 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 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 blacklist_chain=0 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 ) local full=1 if [ "${1}" = "them" -o "${1}" = "him" -o "${1}" = "her" -o "${1}" = "it" -o "${1}" = "this" -o "${1}" = "these" -o "${1}" = "input" ] then shift full=0 elif [ "${1}" = "all" -o "${1}" = "full" ] then shift full=1 fi if [ ${blacklist_chain} -eq 0 ] then set_work_function "Generating blacklist chains" # Blacklist INPUT unidirectional iptables_both -t filter -N BL_IN_UNI # INPUT iptables_both -A BL_IN_UNI -m conntrack --ctstate NEW -j DROP iptables_both -A BL_IN_UNI -j DROP # No need for OUTPUT/FORWARD unidirectional # Blacklist INPUT bidirectional iptables_both -t filter -N BL_IN_BI # INPUT iptables_both -A BL_IN_BI -m conntrack --ctstate NEW -j DROP iptables_both -A BL_IN_BI -j DROP # Blacklist OUTPUT/FORWARD bidirectional iptables_both -t filter -N BL_OUT_BI # OUTPUT and FORWARD iptables_both -A BL_OUT_BI -m conntrack --ctstate NEW -p tcp -j REJECT #--reject-with tcp-reset if running_ipv4; then iptables -A BL_OUT_BI -m conntrack --ctstate NEW -j REJECT --reject-with icmp-host-unreachable fi if running_ipv6; then iptables -A BL_OUT_BI -m conntrack --ctstate NEW -j REJECT --reject-with icmp6-addr-unreachable fi iptables_both -A BL_OUT_BI -j REJECT blacklist_chain=1 fi set_work_function "Generating blacklist rules" local x= for x in ${@//,/ } do set_work_function "Blacklisting '${x}'" case "${x}" in ipset:*) x="${x/ipset:/}" test -z "${FIREHOL_IPSETS_USED[$x]}" && FIREHOL_IPSETS_USED[$x]="USED" if [ ${full} -eq 1 ] then iptables_both -I INPUT -m set --match-set ${x} src -j BL_IN_BI iptables_both -I FORWARD -m set --match-set ${x} src -j BL_IN_BI iptables_both -I OUTPUT -m set --match-set ${x} dst -j BL_OUT_BI iptables_both -I FORWARD -m set --match-set ${x} dst -j BL_OUT_BI else iptables_both -I INPUT -m set --match-set ${x} src -j BL_IN_UNI iptables_both -I FORWARD -m set --match-set ${x} src -j BL_IN_UNI fi ;; *) if [ ${full} -eq 1 ] then iptables_both -I INPUT -s ${x} -j BL_IN_BI iptables_both -I FORWARD -s ${x} -j BL_IN_BI iptables_both -I OUTPUT -d ${x} -j BL_OUT_BI iptables_both -I FORWARD -d ${x} -j BL_OUT_BI else iptables_both -I INPUT -s ${x} -j BL_IN_UNI iptables_both -I FORWARD -s ${x} -j BL_IN_UNI fi ;; esac done return 0 } classify_count=0 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 } 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}" shift 2 test -z "${where}" && where="OUTPUT POSTROUTING" 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 set_work_function "Setting up rules for CONNMARK" local mark="$(mark_value connmark $num)" test -z "${mark}" && work_error=$[work_error + 1] && return 1 local chain= for chain in ${where} do case "${chain}" in interface) if [ ${MARKS_STATEFUL[connmark]} -eq 1 ] then rule table mangle chain PREROUTING custom '-m conntrack --ctstate NEW' inface "${@}" action MARK to "${mark}" || return 1 rule table mangle chain POSTROUTING custom '-m conntrack --ctstate NEW' outface "${@}" action MARK to "${mark}" || return 1 else 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 rule table mangle chain "${chain}" custom '-m conntrack --ctstate NEW' "${@}" action MARK to "${mark}" || return 1 else rule table mangle chain "${chain}" action MARK to "${mark}" || return 1 fi ;; esac done return 0 } 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}" shift 3 test -z "${where}" && where=OUTPUT set_work_function "Setting up rules for MARK" local mark="$(mark_value $name $num)" test -z "${mark}" && work_error=$[work_error + 1] && return 1 if [ ${MARKS_STATEFUL[$name]} -eq 1 ] then rule table mangle chain "${where}" custom '-m conntrack --ctstate NEW' "${@}" action MARK to "${mark}" || return 1 else rule table mangle chain "${where}" "${@}" action MARK to "${mark}" || return 1 fi return 0 } mark() { custommark usermark "${@}" } tos_count=0 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}" shift 2 test -z "${where}" && where=OUTPUT tos_count=$[tos_count + 1] set_work_function "Setting up rules for TOS" create_chain mangle "tos.${tos_count}" "${where}" "$@" || return 1 iptables_both -t mangle -A "tos.${tos_count}" -j TOS --set-tos ${num} return 0 } # from http://blog.edseek.com/~jasonb/articles/traffic_shaping/scenarios.html 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 ) set_work_function "Fixing TOS for TCP ACK packets" iptables_both -t mangle -N ackfix iptables_both -t mangle -A ackfix -m tos ! --tos Normal-Service -j RETURN iptables_both -t mangle -A ackfix -p tcp -m length --length 0:128 -j TOS --set-tos Minimize-Delay iptables_both -t mangle -A ackfix -p tcp -m length --length 128: -j TOS --set-tos Maximize-Throughput iptables_both -t mangle -A ackfix -j RETURN iptables_both -t mangle -I POSTROUTING -p tcp -m tcp --tcp-flags SYN,RST,ACK ACK -j ackfix set_work_function "Fixing TOS for Minimize-Delay packets" iptables_both -t mangle -N tosfix iptables_both -t mangle -A tosfix -p tcp -m length --length 0:512 -j RETURN iptables_both -t mangle -A tosfix -m limit --limit 2/s --limit-burst 10 -j RETURN iptables_both -t mangle -A tosfix -j TOS --set-tos Maximize-Throughput iptables_both -t mangle -A tosfix -j RETURN iptables_both -t mangle -I POSTROUTING -p tcp -m tos --tos Minimize-Delay -j tosfix return 0 } dscp_count=0 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 dscp_count=$[dscp_count + 1] set_work_function "Setting up rules for setting DSCP" create_chain mangle "dscp.${dscp_count}" "${where}" "$@" || return 1 if [ ! -z "${class}" ] then iptables_both -t mangle -A "dscp.${dscp_count}" -j DSCP --set-dscp-class ${class} else iptables_both -t mangle -A "dscp.${dscp_count}" -j DSCP --set-dscp ${value} fi return 0 } 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 --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 --tcp-flags SYN,RST SYN $target done fi return 0 } FIREHOL_IPSET_HASHSIZE_DEFAULT="1024" FIREHOL_IPSET_MAXELEM_DEFAULT="65536" # keep track of all the ipsets the firewall uses declare -A FIREHOL_IPSETS_USED=() # this is a wrapper around ipset # it has the same syntax ipset() { work_realcmd_helper $FUNCNAME "$@" local cmd="${1}" name="${2}" shift 2 if [ "${cmd}" = "create" ] then local type="${1}" inet="inet" hashsize="${FIREHOL_IPSET_HASHSIZE_DEFAULT}" maxelem="${FIREHOL_IPSET_MAXELEM_DEFAULT}" 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="inet6" fi while [ ! -z "${1}" ] do case "${1}" in hashsize) hashsize="${2}" shift ;; maxelem) maxelem="${2}" shift ;; file|ipfile|ipsfile|netfile|netsfile) local file="${2}" final_cmd="${CAT_CMD}" [ "${1}" = "ipfile" -o "${1}" = "ipsfile" ] && final_cmd="${GREP_CMD} -v /" [ "${1}" = "netfile" -o "${1}" = "netsfile" ] && final_cmd="${GREP_CMD} /" shift [ ! -f "${file}" ] && file="${FIREHOL_CONFIG_DIR}/${file}" if [ ! -f "${file}" ] then error "${FUNCNAME}: cannot find file '${file}'." return 1 fi ${CAT_CMD} "${file}" |\ ${SED_CMD} -e "s/#.*$//g" -e "s/[\t\\ ]\+/ /g" -e "s/ \+$//g" -e "s/^ \+//g" |\ ${GREP_CMD} -v "^$" |\ ${final_cmd} |\ ${SORT_CMD} -u >>"${FIREHOL_DIR}/ipset.${name}.ips" ;; *) echo "${1}" >>"${FIREHOL_DIR}/ipset.${name}.ips" ;; esac shift done echo "create ${name} ${type} family ${inet} hashsize ${hashsize} maxelem ${maxelem}" >"${FIREHOL_DIR}/ipset.${name}.rules" echo "flush ${name}" >>"${FIREHOL_DIR}/ipset.${name}.rules" FIREHOL_IPSETS_USED[$name]="CREATED" if [ ! -s "${FIREHOL_DIR}/ipset.${name}.ips" ] then warning "${FUNCNAME} ${name}: does not have any data." return 0 fi while read do echo "add ${name} ${REPLY}" done <"${FIREHOL_DIR}/ipset.${name}.ips" >>"${FIREHOL_DIR}/ipset.${name}.rules" else test -z "${FIREHOL_IPSETS_USED[$name]}" && FIREHOL_IPSETS_USED[$name]="USED" postprocess ${IPSET_CMD} ${cmd} ${name} "${@}" fi } ipsets_apply() { local from="${1}" base="${FIREHOL_DIR}" only_non_existing=0 x= list # 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 [ -z "${IPSET_CMD}" ] then require_cmd ipset || exit 1 list=$( ${IPSET_CMD} list -n 2>/dev/null ) if [ $? -ne 0 ] then echo >&2 "Your ipset command ${IPSET_CMD} does not seem to be a recent version. Please upgrade to use ipset with FireHOL." exit 1 fi fi if [ "${from}" = "spool" ] then base="${FIREHOL_SPOOL_DIR}" only_non_existing=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 echo >&2 -n "FireHOL: Restoring ipsets from ${FIREHOL_SPOOL_DIR}... " else echo >&2 -n "FireHOL: Activating ipsets... " fi # take a list of all active ipsets # and mark each one that we have too as existing for x in ${list} 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 $only_non_existing -eq 1 && continue # add all the rules except the create one ${CAT_CMD} "${base}/ipset.${x}.rules" |\ ${GREP_CMD} -v "^create ${x}" >>"${FIREHOL_DIR}/ipsets.restore" else # 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}" restore <"${FIREHOL_DIR}/ipsets.restore" if [ $? -ne 0 ] then error "${FUNCNAME}: Cannot apply generated ipset rules." return 1 else echo >&2 "OK" 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_SPOOL_DIR}/ipsets.conf" cp "${FIREHOL_DIR}/ipsets.restore" "${FIREHOL_SPOOL_DIR}/last.ipsets.restore" ${IPSET_CMD} save >"${FIREHOL_SPOOL_DIR}/last.ipsets.save" fi else echo >&2 "OK (already exist)" 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 in set_work_inface "$@" inface "${inface}" outface any || return 1 create_chain filter "out_${work_name}" OUTPUT 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 in set_work_inface set_work_outface "$@" || return 1 create_chain filter "out_${work_name}" FORWARD 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 test "${FIREHOL_MODE}" = "DEBUG" && check="debug" test "${FIREHOL_MODE}" = "EXPLAIN" && check="none" 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 if [ "${FIREHOL_MODE}" = "EXPLAIN" ] then file close 21 ${CAT_CMD} "${FIREHOL_OUTPUT}" ${RM_CMD} "${FIREHOL_OUTPUT}" file open 21 "${FIREHOL_OUTPUT}" w || exit 1 fi 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 "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}" "$@" FIREHOL_COMMAND_COUNTER=$[FIREHOL_COMMAND_COUNTER + 1] fi 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) iptables_both -A "${in}_${work_name}" -m conntrack --ctstate INVALID -j DROP || return 1 ;; fragments|FRAGMENTS) 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 " IMPORTANT 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, accept all related packets, 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}'" # Accept all related traffic to the established connections rule chain "in_${work_name}" state RELATED action ACCEPT || return 1 rule chain "out_${work_name}" state RELATED action ACCEPT || return 1 # make sure we have a policy test -z "${work_policy}" && work_policy="${DEFAULT_INTERFACE_POLICY}" case "${work_policy}" in return|RETURN) return 0 ;; accept|ACCEPT) ;; *) if [ "${FIREHOL_DROP_ORPHAN_TCP_ACK_FIN}" = "1" ] then # Silently drop orphan TCP/ACK FIN packets rule chain "in_${work_name}" proto tcp custom "--tcp-flags ALL ACK,FIN" action DROP || return 1 rule reverse chain "out_${work_name}" proto tcp custom "--tcp-flags ALL ACK,FIN" action DROP || return 1 fi local -a inlog=(loglimit "IN-${work_name}") local -a outlog=(loglimit "OUT-${work_name}") ;; esac 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}'" # Accept all related traffic to the established connections rule chain "in_${work_name}" state RELATED action ACCEPT || return 1 rule chain "out_${work_name}" state RELATED action ACCEPT || return 1 # make sure we have a policy test -z "${work_policy}" && work_policy="${DEFAULT_ROUTER_POLICY}" case "${work_policy}" in return|RETURN) return 0 ;; accept|ACCEPT) ;; *) if [ "${FIREHOL_DROP_ORPHAN_TCP_ACK_FIN}" = "1" ] then # Silently drop orphan TCP/ACK FIN packets rule chain "in_${work_name}" proto tcp custom "--tcp-flags ALL ACK,FIN" action DROP || return 1 rule reverse chain "out_${work_name}" proto tcp custom "--tcp-flags ALL ACK,FIN" action DROP || return 1 fi local -a inlog=(loglimit "PASS-${work_name}") local -a outlog=(loglimit "PASS-${work_name}") ;; esac 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" if [ ! "${MARKS_SAVERESTORE_STATEFUL_MASK}" = "0x00000000" ] then # 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} # 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 # 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} # 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 # Accept all related traffic to the established connections rule chain INPUT state RELATED action ACCEPT || return 1 rule chain OUTPUT state RELATED action ACCEPT || return 1 rule chain FORWARD state RELATED action ACCEPT || return 1 if [ "${FIREHOL_DROP_ORPHAN_TCP_ACK_FIN}" = "1" ] then # Silently drop orphan TCP/ACK FIN packets rule chain INPUT proto tcp custom "--tcp-flags ALL ACK,FIN" action DROP || return 1 rule chain OUTPUT proto tcp custom "--tcp-flags ALL ACK,FIN" action DROP || return 1 rule chain FORWARD proto tcp custom "--tcp-flags ALL ACK,FIN" action DROP || return 1 fi 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 } 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 create_chain filter "in_${mychain}" "in_${work_name}" in "$@" || return 1 create_chain filter "out_${mychain}" "out_${work_name}" 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] 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 FIREHOL_ACCEPT_CHAIN_COUNT=0 rule_action_param() { local iptables_cmd="${1}" \ action="${2}" \ protocol="${3}" \ statenot="${4}" \ state="${5}" \ table="${6}" \ count=0 val= shift 6 local -a action_param=() # All arguments until the separator are the parameters of the action for val in "${@}" do [ "A${val}" = "A--" ] && break action_param[$count]="${val}" ((count += 1)) done shift $[count + 1] # If we don't have a seperator, generate an error if [ ! "A${val}" = "A--" ] then error "Internal Error, in parsing action_param parameters ($FUNCNAME '${action}' '${protocol}' '${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 ${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 ${table} -A "${accept_limit_chain}" -m conntrack ! --ctstate NEW -j ACCEPT # accept NEW connections within the given limits. $iptables_cmd ${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_PREFIX}LIMIT_OVERFLOW:") elif [ "${FIREHOL_LOG_MODE}" = "NFLOG" ] then logopts_arg=("--nflog-prefix=${FIREHOL_LOG_PREFIX}LIMIT_OVERFLOW:") else logopts_arg=("--log-level" "${FIREHOL_LOG_LEVEL}" "--log-prefix=${FIREHOL_LOG_PREFIX}LIMIT_OVERFLOW:") fi $iptables_cmd ${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 ${table} -A "${accept_limit_chain}" -p tcp -j REJECT --reject-with tcp-reset fi # do the specified action on the overflow $iptables_cmd ${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 ${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 ${table} -A "${accept_recent_chain}" -m conntrack ! --ctstate NEW -j ACCEPT # accept NEW connections within the given limits. $iptables_cmd ${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 ${table} -A "${accept_recent_chain}" -m recent --update ${t1} ${t2} --name "${name}" -j RETURN $iptables_cmd ${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 ${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 ;; REJECT) if [ "${action_param[1]}" = "auto" ] then if [ "${protocol}" = "tcp" -o "${protocol}" = "TCP" ] then action_param=("--reject-with" "tcp-reset") else action_param=() fi fi ;; esac $iptables_cmd "$@" -j "${action}" "${action_param[@]}" } rule() { # 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= \ pid=any pidnot= sid=any sidnot= \ cmd=any cmdnot= \ mark=any marknot= markname= \ dscp=any dscptype= dscpnot= \ tos=any tosnot= \ log= logtxt= loglevel= \ limit= burst= iplimit= iplimit_mask= \ action= state= statenot= \ failed=0 reverse=0 \ swi=0 swo=0 \ custom= \ accounting= # 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 overwritting optional rule # parameters will take place. local softwarnings=1 # set it, in order to be local local -a action_param=() while [ ! -z "${1}" ] do case "${1}" in reverse|REVERSE) reverse=1 shift ;; table|TABLE) test ${softwarnings} -eq 1 -a ! -z "${table}" && softwarning "Overwritting param: ${1} '${chain}' becomes '${2}'" table="-t ${2}" shift 2 ;; chain|CHAIN) test ${softwarnings} -eq 1 -a ! -z "${chain}" && softwarning "Overwritting param: ${1} '${chain}' becomes '${2}'" chain="${2}" shift 2 ;; inface|INFACE) shift if [ ${reverse} -eq 0 ] then infacenot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift infacenot="!" else if [ ${swi} -eq 1 ] then work_inface="${1}" fi fi test ${softwarnings} -eq 1 -a ! "${inface}" = "any" && softwarning "Overwritting param: inface '${inface}' becomes '${1}'" inface="${1//,/ }" else outfacenot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift outfacenot="!" else if [ ${swo} -eq 1 ] then work_outface="$1" fi fi test ${softwarnings} -eq 1 -a ! "${outface}" = "any" && softwarning "Overwritting param: outface '${outface}' becomes '${1}'" outface="${1//,/ }" fi shift ;; outface|OUTFACE) shift if [ ${reverse} -eq 0 ] then outfacenot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift outfacenot="!" else if [ ${swo} -eq 1 ] then work_outface="${1}" fi fi test ${softwarnings} -eq 1 -a ! "${outface}" = "any" && softwarning "Overwritting param: outface '${outface}' becomes '${1}'" outface="${1//,/ }" else infacenot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift infacenot="!" else if [ ${swi} -eq 1 ] then work_inface="${1}" fi fi test ${softwarnings} -eq 1 -a ! "${inface}" = "any" && softwarning "Overwritting param: inface '${inface}' becomes '${1}'" inface="${1//,/ }" fi shift ;; physin|PHYSIN) shift if [ ${reverse} -eq 0 ] then physinnot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift physinnot="!" fi test ${softwarnings} -eq 1 -a ! "${physin}" = "any" && softwarning "Overwritting param: physin '${physin}' becomes '${1}'" physin="${1//,/ }" else physoutnot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift physoutnot="!" fi test ${softwarnings} -eq 1 -a ! "${physout}" = "any" && softwarning "Overwritting param: physout '${physout}' becomes '${1}'" physout="${1//,/ }" fi shift ;; physout|PHYSOUT) shift if [ ${reverse} -eq 0 ] then physoutnot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift physoutnot="!" fi test ${softwarnings} -eq 1 -a ! "${physout}" = "any" && softwarning "Overwritting param: physout '${physout}' becomes '${1}'" physout="${1//,/ }" else physinnot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift physinnot="!" fi test ${softwarnings} -eq 1 -a ! "${physin}" = "any" && softwarning "Overwritting param: physin '${physin}' becomes '${1}'" physin="${1//,/ }" fi shift ;; mac|MAC) shift macnot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift test ${nomac} -eq 0 && macnot="!" fi test ${softwarnings} -eq 1 -a ! "${mac}" = "any" && softwarning "Overwritting param: mac '${mac}' becomes '${1}'" test ${nomac} -eq 0 && mac="${1//,/ }" shift ;; src|SRC|source|SOURCE|src4|src6) if [ "${1}" = "src4" ] then if ! push_namespace ipv4; then return 1; fi elif [ "${1}" = "src6" ] then if ! push_namespace ipv6; then return 1; fi else push_namespace "${FIREHOL_NS_CURR}" fi shift if [ ${reverse} -eq 0 ] then running_ipv4 && src4not= running_ipv6 && src6not= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift running_ipv4 && src4not="!" running_ipv6 && src6not="!" fi if running_ipv4; then test ${softwarnings} -eq 1 -a ! "${src4}" = "default" && softwarning "Overwritting param: src4 '${src4}' becomes '${1}'" src4="${1//,/ }" fi if running_ipv6; then test ${softwarnings} -eq 1 -a ! "${src6}" = "default" && softwarning "Overwritting param: src6 '${src6}' becomes '${1}'" src6="${1//,/ }" fi else running_ipv4 && dst4not= running_ipv6 && dst6not= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift running_ipv4 && dst4not="!" running_ipv6 && dst6not="!" fi if running_ipv4; then test ${softwarnings} -eq 1 -a ! "${dst4}" = "default" && softwarning "Overwritting param: dst4 '${dst4}' becomes '${1}'" dst4="${1//,/ }" fi if running_ipv6; then test ${softwarnings} -eq 1 -a ! "${dst6}" = "default" && softwarning "Overwritting param: dst6 '${dst6}' becomes '${1}'" dst6="${1//,/ }" fi fi pop_namespace shift ;; dst|DST|destination|DESTINATION|dst4|dst6) if [ "${1}" = "dst4" ] then if ! push_namespace ipv4; then return 1; fi elif [ "${1}" = "dst6" ] then if ! push_namespace ipv6; then return 1; fi else push_namespace "${FIREHOL_NS_CURR}" fi shift if [ ${reverse} -eq 0 ] then running_ipv4 && dst4not= running_ipv6 && dst6not= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift running_ipv4 && dst4not="!" running_ipv6 && dst6not="!" fi if running_ipv4; then test ${softwarnings} -eq 1 -a ! "${dst4}" = "default" && softwarning "Overwritting param: dst4 '${dst4}' becomes '${1}'" dst4="${1//,/ }" fi if running_ipv6; then test ${softwarnings} -eq 1 -a ! "${dst6}" = "default" && softwarning "Overwritting param: dst6 '${dst6}' becomes '${1}'" dst6="${1//,/ }" fi else running_ipv4 && src4not= running_ipv6 && src6not= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift running_ipv4 && src4not="!" running_ipv6 && src6not="!" fi if running_ipv4; then test ${softwarnings} -eq 1 -a ! "${src4}" = "default" && softwarning "Overwritting param: src6 '${src4}' becomes '${1}'" src4="${1//,/ }" fi if running_ipv6; then test ${softwarnings} -eq 1 -a ! "${src6}" = "default" && softwarning "Overwritting param: src6 '${src6}' becomes '${1}'" src6="${1//,/ }" fi fi pop_namespace shift ;; srctype|SRCTYPE|sourcetype|SOURCETYPE) shift if [ ${reverse} -eq 0 ] then srctypenot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift srctypenot="!" fi test ${softwarnings} -eq 1 -a ! "${srctype}" = "" && softwarning "Overwritting param: srctype '${srctype}' becomes '${1}'" srctype="`echo ${1} | ${SED_CMD} -e "s|^ \+||" -e "s| \+\$||" -e "s| \+|,|g" | ${TR_CMD} a-z A-Z`" else dsttypenot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift dsttypenot="!" fi test ${softwarnings} -eq 1 -a ! "${dsttype}" = "" && softwarning "Overwritting param: dsttype '${dsttype}' becomes '${1}'" dsttype="`echo ${1} | ${SED_CMD} -e "s|^ \+||" -e "s| \+\$||" -e "s| \+|,|g" | ${TR_CMD} a-z A-Z`" fi shift ;; dsttype|DSTTYPE|destinationtype|DESTINATIONTYPE) shift if [ ${reverse} -eq 0 ] then dsttypenot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift dsttypenot="!" fi test ${softwarnings} -eq 1 -a ! "${dsttype}" = "" && softwarning "Overwritting param: dsttype '${dsttype}' becomes '${1}'" dsttype="`echo ${1} | ${SED_CMD} -e "s|^ \+||" -e "s| \+\$||" -e "s| \+|,|g" | ${TR_CMD} a-z A-Z`" else srctypenot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift srctypenot="!" fi test ${softwarnings} -eq 1 -a ! "${srctype}" = "" && softwarning "Overwritting param: srctype '${srctype}' becomes '${1}'" srctype="`echo ${1} | ${SED_CMD} -e "s|^ \+||" -e "s| \+\$||" -e "s| \+|,|g" | ${TR_CMD} a-z A-Z`" fi shift ;; sport|SPORT|sourceport|SOURCEPORT) shift if [ ${reverse} -eq 0 ] then sportnot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift sportnot="!" fi test ${softwarnings} -eq 1 -a ! "${sport}" = "any" && softwarning "Overwritting param: sport '${sport}' becomes '${1}'" sport="${1//,/ }" else dportnot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift dportnot="!" fi test ${softwarnings} -eq 1 -a ! "${dport}" = "any" && softwarning "Overwritting param: dport '${dport}' becomes '${1}'" dport="${1//,/ }" fi shift ;; dport|DPORT|destinationport|DESTINATIONPORT) shift if [ ${reverse} -eq 0 ] then dportnot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift dportnot="!" fi test ${softwarnings} -eq 1 -a ! "${dport}" = "any" && softwarning "Overwritting param: dport '${dport}' becomes '${1}'" dport="${1//,/ }" else sportnot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift sportnot="!" fi test ${softwarnings} -eq 1 -a ! "${sport}" = "any" && softwarning "Overwritting param: sport '${sport}' becomes '${1}'" sport="${1//,/ }" fi shift ;; proto|PROTO|protocol|PROTOCOL) shift protonot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift protonot="!" fi test ${softwarnings} -eq 1 -a ! "${proto}" = "any" && softwarning "Overwritting param: proto '${proto}' becomes '${1}'" proto="${1//,/ }" shift ;; custommark|CUSTOMMARK) shift markname="${1}" shift marknot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift marknot="!" fi test ${softwarnings} -eq 1 -a ! "${mark}" = "any" && softwarning "Overwritting param: mark '${mark}' becomes ${markname} '${1}'" mark="${mark} $(mark_value $markname ${1//,/ })" shift ;; mark|MARK) shift marknot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift marknot="!" fi test ${softwarnings} -eq 1 -a ! "${mark}" = "any" && softwarning "Overwritting param: mark '${mark}' becomes usermark '${1}'" mark="${mark} $(mark_value usermark ${1//,/ })" shift ;; connmark|CONNMARK) shift marknot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift marknot="!" fi test ${softwarnings} -eq 1 -a ! "${mark}" = "any" && softwarning "Overwritting param: mark '${mark}' becomes connmark '${1}'" mark="${mark} $(mark_value connmark ${1//,/ })" shift ;; rawmark|RAWMARK) shift marknot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift marknot="!" fi test ${softwarnings} -eq 1 -a ! "${mark}" = "any" && softwarning "Overwritting param: mark '${mark}' becomes '${1}'" mark="${1//,/ }" shift ;; tos|TOS) shift tosnot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift tosnot="!" fi test ${softwarnings} -eq 1 -a ! "${tos}" = "any" && softwarning "Overwritting param: tos '${tos}' becomes '${1}'" tos="${1//,/ }" shift ;; dscp|DSCP) shift dscpnot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift dscpnot="!" fi test ${softwarnings} -eq 1 -a ! "${dscp}" = "any" && softwarning "Overwritting param: dscp '${dscp}' becomes '${1}'" dscp="${1//,/ }" shift if [ "${dscp}" = "class" ] then dscptype="-class" dscp="${1//,/ }" shift fi ;; action|ACTION) test ${softwarnings} -eq 1 -a ! -z "${action}" && softwarning "Overwritting param: action '${action}' becomes '${2}'" action="${2}" shift 2 action_param=() local action_is_chain=0 case "${action}" in accept|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 ;; deny|DENY|drop|DROP) action="DROP" ;; reject|REJECT) action="REJECT" if [ "${1}" = "with" ] then action_param=("--reject-with" "${2}") shift 2 else action_param=("--reject-with" "auto") fi ;; return|RETURN) action="RETURN" ;; mirror|MIRROR) action="MIRROR" test $nomirror -eq 1 && action="REJECT" ;; none|NONE) action="NONE" ;; snat|SNAT) action="SNAT" if [ "${1}" = "to" ] then action_param=() local x= for x in ${2} do action_param=("${action_param[@]}" "--to-source" "${x}") done shift 2 else error "${action} requires a 'to' argument." return 1 fi if [ ! "A${table}" = "A-t nat" ] then error "${action} must on a the 'nat' table." return 1 fi ;; dnat|DNAT) action="DNAT" if [ "${1}" = "to" ] then action_param=() local x= for x in ${2} do action_param=("${action_param[@]}" "--to-destination" "${x}") done shift 2 else error "${action} requires a 'to' argument" return 1 fi if [ ! "A${table}" = "A-t nat" ] then error "${action} must on a the 'nat' table." return 1 fi ;; redirect|REDIRECT) action="REDIRECT" if [ "${1}" = "on-port" -o "${1}" = "to-port" -o "${1}" = "to" ] then action_param=("--to-ports" "${2}") shift 2 else error "${action} requires a 'to-port' or 'to' argument." return 1 fi if [ ! "A${table}" = "A-t nat" ] then error "${action} must on a the 'nat' table." return 1 fi ;; tproxy|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 [ ! "A${table}" = "A-t mangle" ] then error "${action} cannot be on '$table', only on a the 'mangle' table." return 1 fi ;; tos|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 [ ! "A${table}" = "A-t mangle" ] then error "${action} must on a the 'mangle' table." return 1 fi ;; mark|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 [ ! "A${table}" = "A-t mangle" ] then error "${action} must on a the 'mangle' table." return 1 fi ;; connmark|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 [ ! "A${table}" = "A-t mangle" ] then error "${action} must on a the 'mangle' table." return 1 fi ;; dscp|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 [ ! "A${table}" = "A-t mangle" ] then error "${action} must on a the 'mangle' table." return 1 fi ;; tarpit|TARPIT) action="TARPIT" ;; *) chain_exists "${action}" local action_is_chain=$? ;; esac ;; state|STATE) shift statenot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift statenot="!" fi test ${softwarnings} -eq 1 -a ! -z "${state}" && softwarning "Overwritting param: state '${state}' becomes '${1}'" state="${1}" shift ;; user|USER|uid|UID) shift uidnot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift test ${noowner} -eq 0 && uidnot="!" fi test ${softwarnings} -eq 1 -a ! "${uid}" = "any" && softwarning "Overwritting param: uid '${uid}' becomes '${1}'" test ${noowner} -eq 0 && uid="${1//,/ }" shift ;; group|GROUP|gid|GID) shift gidnot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift test ${noowner} -eq 0 && gidnot="!" fi test ${softwarnings} -eq 1 -a ! "${gid}" = "any" && softwarning "Overwritting param: gid '${gid}' becomes '${1}'" test ${noowner} -eq 0 && gid="${1//,/ }" shift ;; process|PROCESS|pid|PID) shift pidnot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift test ${noowner} -eq 0 && pidnot="!" fi test ${softwarnings} -eq 1 -a ! "${pid}" = "any" && softwarning "Overwritting param: pid '${pid}' becomes '${1}'" test ${noowner} -eq 0 && pid="${1//,/ }" shift ;; session|SESSION|sid|SID) shift sidnot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift test ${noowner} -eq 0 && sidnot="!" fi test ${softwarnings} -eq 1 -a ! "${sid}" = "any" && softwarning "Overwritting param: sid '${sid}' becomes '${1}'" test ${noowner} -eq 0 && sid="${1//,/ }" shift ;; command|COMMAND|cmd|CMD) shift cmdnot= if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift test ${noowner} -eq 0 && cmdnot="!" fi test ${softwarnings} -eq 1 -a ! "${cmd}" = "any" && softwarning "Overwritting param: cmd '${cmd}' becomes '${1}'" test ${noowner} -eq 0 && cmd="${1}" shift ;; custom|CUSTOM) test ${softwarnings} -eq 1 -a ! -z "${custom}" && softwarning "Overwritting param: custom '${custom}' becomes '${2}'" custom="${2}" shift 2 ;; log|LOG) if [ ${nolog} -eq 0 ] then test ${softwarnings} -eq 1 -a ! -z "${log}" && softwarning "Overwritting param: log '${log}/${logtxt}' becomes 'normal/${2}'" log=normal logtxt="`echo ${2} | ${TR_CMD} " " "_"`" fi shift 2 if [ "${1}" = "level" ] then loglevel="${2}" shift 2 else loglevel="${FIREHOL_LOG_LEVEL}" fi ;; loglimit|LOGLIMIT) if [ ${nolog} -eq 0 ] then test ${softwarnings} -eq 1 -a ! -z "${log}" && softwarning "Overwritting param: log '${log}/${logtxt}' becomes 'limit/${2}'" log=limit logtxt="`echo ${2} | ${TR_CMD} " " "_"`" fi shift 2 if [ "${1}" = "level" ] then loglevel="${2}" shift 2 else loglevel="${FIREHOL_LOG_LEVEL}" fi ;; limit|LIMIT) test ${softwarnings} -eq 1 -a ! -z "${limit}" && softwarning "Overwritting param: limit '${limit}' becomes '${2}'" limit="${2}" burst="${3}" shift 3 ;; iplimit|IPLIMIT) test ${softwarnings} -eq 1 -a ! -z "${iplimit}" && softwarning "Overwritting param: iplimit '${iplimit}' becomes '${2}'" iplimit="${2}" iplimit_mask="${3}" shift 3 ;; in) # this is incoming traffic - ignore packet ownership noowner=1 nomirror=0 nomac=0 shift ;; out) # this is outgoing traffic - ignore packet ownership if not in an interface if [ ! "${work_cmd}" = "interface" ] then noowner=1 else nomirror=1 fi nomac=1 shift ;; nolog) nolog=1 shift ;; noowner) noowner=1 shift ;; softwarnings) softwarnings=1 shift ;; nosoftwarnings) softwarnings=0 shift ;; set_work_inface|SET_WORK_INFACE) swi=1 shift ;; set_work_outface|SET_WORK_OUTFACE) swo=1 shift ;; acct|accounting) 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 2 ;; *) error "Cannot understand directive '${1}'." return 1 ;; esac done test -z "${table}" && table="-t filter" # 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. # The special case here is the protocol "any". # To accomplish the differentiation based on protocol we have # to change the protocol "any" to "tcp any" test "${action}" = "REJECT" -a "${action_param[1]}" = "auto" -a "${proto}" = "any" && proto="tcp any" # we cannot accept empty strings to a few parameters, since this # will prevent us from generating a rule (due to nested BASH loops). test -z "${inface}" && error "Cannot accept an empty 'inface'." && return 1 test -z "${outface}" && error "Cannot accept an empty 'outface'." && return 1 test -z "${physin}" && error "Cannot accept an empty 'physin'." && return 1 test -z "${physout}" && error "Cannot accept an empty 'physout'." && return 1 test -z "${mac}" && error "Cannot accept an empty 'mac'." && return 1 test -z "${src4}" && error "Cannot accept an empty 'src4'." && return 1 test -z "${dst4}" && error "Cannot accept an empty 'dst4'." && return 1 test -z "${src6}" && error "Cannot accept an empty 'src6'." && return 1 test -z "${dst6}" && error "Cannot accept an empty 'dst6'." && return 1 test -z "${sport}" && error "Cannot accept an empty 'sport'." && return 1 test -z "${dport}" && error "Cannot accept an empty 'dport'." && return 1 test -z "${proto}" && error "Cannot accept an empty 'proto'." && return 1 test -z "${uid}" && error "Cannot accept an empty 'uid'." && return 1 test -z "${gid}" && error "Cannot accept an empty 'gid'." && return 1 test -z "${pid}" && error "Cannot accept an empty 'pid'." && return 1 test -z "${sid}" && error "Cannot accept an empty 'sid'." && return 1 test -z "${cmd}" && error "Cannot accept an empty 'cmd'." && return 1 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 # ---------------------------------------------------------------------------------- # Do we have negative contitions? # If yes, we have to: # # case 1: If the action is a chain. # Add to this chain positive RETURN statements matching all the negatives. # The positive rules will be added bellow to the same chain and will be # matched only if all RETURNs have not been matched. # # case 2: If the action is not a chain. # Create a temporary chain, then add to this chain positive RETURN rules # matching the negatives, and append at its end the final action (which is # not a chain), then change the action of the positive rules to jump to # this temporary chain. # ignore 'statenot', 'srctypenot', 'dsttypenot' since it is negated in the positive rules if [ ! -z "${infacenot}${outfacenot}${physinnot}${physoutnot}${macnot}${srcnot}${dstnot}${sportnot}${dportnot}${protonot}${uidnot}${gidnot}${pidnot}${sidnot}${cmdnot}${marknot}${tosnot}${dscpnot}" ] then if [ ${action_is_chain} -eq 1 ] then # if the action is a chain name, then just add the negative # expressions to this chain. Nothing more. local negative_chain="${action}" negative_action= 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. local DYNAMIC_CHAIN_COUNTER get_next_dynamic_counter DYNAMIC_CHAIN_COUNTER negative_chain="${chain}.${DYNAMIC_CHAIN_COUNTER}" iptables_both ${table} -N "${negative_chain}" negative_action="${action}" action="${negative_chain}" fi if [ ! -z "${infacenot}" ] then local inf= for inf in ${inface} do iptables_both ${table} -A "${negative_chain}" -i "${inf}" -j RETURN done infacenot= inface=any fi if [ ! -z "${outfacenot}" ] then local outf= for outf in ${outface} do iptables_both ${table} -A "${negative_chain}" -o "${outf}" -j RETURN done outfacenot= outface=any fi if [ ! -z "${physinnot}" ] then local inph= for inph in ${physin} do iptables_both ${table} -A "${negative_chain}" -m physdev ${physbridge} --physdev-in "${inph}" -j RETURN done physinnot= physin=any fi if [ ! -z "${physoutnot}" ] then local outph= for outph in ${physout} do iptables_both ${table} -A "${negative_chain}" -m physdev ${physbridge} --physdev-out "${outph}" -j RETURN done physoutnot= physout=any fi if [ ! -z "${macnot}" ] then local m= for m in ${mac} do iptables_both ${table} -A "${negative_chain}" -m mac --mac-source "${m}" -j RETURN done macnot= mac=any fi if [ ! -z "${srcnot}" ] then local s= src= iptables= running_ipv6 && { iptables="ip6tables"; src="${src6}"; } running_ipv4 && { iptables="iptables"; src="${src4}"; } for s in ${src} do case "${s}" in ipset:*) s="${s/ipset:/}" test -z "${FIREHOL_IPSETS_USED[$s]}" && FIREHOL_IPSETS_USED[$s]="USED" ${iptables} ${table} -A "${negative_chain}" -m set --match-set "${s}" src -j RETURN ;; *) ${iptables} ${table} -A "${negative_chain}" -s "${s}" -j RETURN ;; esac done srcnot= src4=any src6=any fi if [ ! -z "${dstnot}" ] then local d= dst= iptables= running_ipv6 && { iptables="ip6tables"; dst="${dst6}"; } running_ipv4 && { iptables="iptables"; dst="${dst4}"; } for d in ${dst} do case "${d}" in ipset:*) d="${d/ipset:/}" test -z "${FIREHOL_IPSETS_USED[$d]}" && FIREHOL_IPSETS_USED[$d]="USED" ${iptables} ${table} -A "${negative_chain}" -m set --match-set "${d}" dst -j RETURN ;; *) ${iptables} ${table} -A "${negative_chain}" -d "${d}" -j RETURN ;; esac done dstnot= dst4=any dst6=any fi if [ ! -z "${protonot}" ] then if [ ! -z "${sportnot}" -o ! -z "${dportnot}" ] then error "Cannot have negative protocol(s) and source/destination port(s)." return 1 fi local pr= for pr in ${proto} do iptables_both ${table} -A "${negative_chain}" -p "${pr}" -j RETURN done protonot= proto=any fi if [ ! -z "${sportnot}" ] then if [ "${proto}" = "any" ] then error "Cannot have negative source port specification without protocol." return 1 fi local sp= pr= for sp in ${sport} do for pr in ${proto} do iptables_both ${table} -A "${negative_chain}" -p "${pr}" --sport "${sp}" -j RETURN done done sportnot= sport=any fi if [ ! -z "${dportnot}" ] then if [ "${proto}" = "any" ] then error "Cannot have negative destination port specification without protocol." return 1 fi local dp= pr= for dp in ${dport} do for pr in ${proto} do iptables_both ${table} -A "${negative_chain}" -p "${pr}" --dport "${dp}" -j RETURN done done dportnot= dport=any fi if [ ! -z "${uidnot}" ] then local tuid= for tuid in ${uid} do iptables_both ${table} -A "${negative_chain}" -m owner --uid-owner "${tuid}" -j RETURN done uidnot= uid=any fi if [ ! -z "${gidnot}" ] then local tgid= for tgid in ${gid} do iptables_both ${table} -A "${negative_chain}" -m owner --gid-owner "${tgid}" -j RETURN done gidnot= gid=any fi if [ ! -z "${pidnot}" ] then local tpid= for tpid in ${pid} do iptables_both ${table} -A "${negative_chain}" -m owner --pid-owner "${tpid}" -j RETURN done pidnot= pid=any fi if [ ! -z "${sidnot}" ] then local tsid= for tsid in ${sid} do iptables_both ${table} -A "${negative_chain}" -m owner --sid-owner "${tsid}" -j RETURN done sidnot= sid=any fi if [ ! -z "${cmdnot}" ] then local tcmd= for tcmd in ${cmd} do iptables_both ${table} -A "${negative_chain}" -m owner --cmd-owner "${tcmd}" -j RETURN done cmdnot= cmd=any fi if [ ! -z "${marknot}" ] then local tmark= for tmark in ${mark} do iptables_both ${table} -A "${negative_chain}" -m mark --mark "${tmark}" -j RETURN done marknot= mark=any fi if [ ! -z "${tosnot}" ] then local ttos= for ttos in ${tos} do iptables_both ${table} -A "${negative_chain}" -m tos --tos "${ttos}" -j RETURN done tosnot= tos=any fi if [ ! -z "${dscpnot}" ] then local tdscp= for tdscp in ${dscp} do iptables_both ${table} -A "${negative_chain}" -m dscp --dscp${dscptype} "${tdscp}" -j RETURN done dscp=any dscpnot= fi # in case this is temporary chain we created for the negative expression, # just make it have the final action of the rule. if [ ! -z "${negative_action}" ] then local pr= local -a proto_arg=() for pr in ${proto} do case ${pr} in any|ANY) ;; *) proto_arg=("-p" "${pr}") ;; esac if running_ipv4; then rule_action_param iptables "${negative_action}" "${pr}" "" "" "${table}" "${action_param[@]}" -- ${table} -A "${negative_chain}" "${proto_arg[@]}" || failed=$[failed + 1] fi if running_ipv6; then rule_action_param ip6tables "${negative_action}" "${pr}" "" "" "${table}" "${action_param[@]}" -- ${table} -A "${negative_chain}" "${proto_arg[@]}" || failed=$[failed + 1] fi action_param=() done fi fi # ---------------------------------------------------------------------------------- # Process the positive rules local -a \ addrtype_arg=() stp_arg=() dtp_arg=() state_arg=() \ limit_arg=() iplimit_arg=() logopts_arg=() \ uid_arg=() owner_arg=() gid_arg=() pid_arg=() sid_arg=() cmd_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=() local logrule= ipvall= \ tuid= tgid= tpid= tsid= tcmd= \ tmark= ttos= tdscp= pr= \ inf= outf= inph= outph= \ mc= ipv= iptables= src= dst= s= d= sp= dp= # addrtype (srctype, dsttype) 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}") # limit [ ! -z "${limit}" ] && limit_arg=("-m" "limit" "--limit" "${limit}" "--limit-burst" "${burst}") # iplimit [ ! -z "${iplimit}" ] && iplimit_arg=("-m" "iplimit" "--iplimit-above" "${iplimit}" "--iplimit-mask" "${iplimit_mask}") # log mode selection if [ "${FIREHOL_LOG_MODE}" = "ULOG" ] then logopts_arg=("--ulog-prefix=${FIREHOL_LOG_PREFIX}${logtxt}:") elif [ "${FIREHOL_LOG_MODE}" = "NFLOG" ] then logopts_arg=("--nflog-prefix=${FIREHOL_LOG_PREFIX}${logtxt}:") else logopts_arg=("--log-level" "${loglevel}" "--log-prefix=${FIREHOL_LOG_PREFIX}${logtxt}:") 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" # uid for tuid in ${uid} do case ${tuid} in any|ANY) uid_arg=() owner_arg=() ;; *) owner_arg=("-m" "owner") uid_arg=("--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=("--gid-owner" "${tgid}") ;; esac # pid for tpid in ${pid} do case ${tpid} in any|ANY) pid_arg=() # do not reset owner_arg=() here ;; *) owner_arg=("-m" "owner") pid_arg=("--pid-owner" "${tpid}") ;; esac # sid for tsid in ${sid} do case ${tsid} in any|ANY) sid_arg=() # do not reset owner_arg=() here ;; *) owner_arg=("-m" "owner") sid_arg=("--sid-owner" "${tsid}") ;; esac # cmd for tcmd in ${cmd} do case ${tcmd} in any|ANY) cmd_arg=() # do not reset owner_arg=() here ;; *) owner_arg=("-m" "owner") cmd_arg=("--cmd-owner" "${tcmd}") ;; esac # mark for tmark in ${mark} do case ${tmark} in any|ANY) mark_arg=() ;; *) mark_arg=("-m" "mark" "--mark" "${tmark}") ;; esac # tos for ttos in ${tos} do case ${ttos} in any|ANY) tos_arg=() ;; *) tos_arg=("-m" "tos" "--tos" "${ttos}") ;; esac # dscp for tdscp in ${dscp} do case ${tdscp} in any|ANY) dscp_arg=() ;; *) dscp_arg=("-m" "dscp" "--dscp${dscptype}" "${tdscp}") ;; esac # proto for pr in ${proto} do case ${pr} in any|ANY) proto_arg=() ;; *) proto_arg=("-p" "${pr}") ;; esac # inface for inf in ${inface} do case ${inf} in any|ANY) inf_arg=() ;; *) inf_arg=("-i" "${inf}") ;; esac # outface for outf in ${outface} do case ${outf} in any|ANY) outf_arg=() ;; *) outf_arg=("-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=("--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=("--physdev-out" "${outph}") ;; esac # mac for mc in ${mac} do case ${mc} in any|ANY) mc_arg=() ;; *) mc_arg=("-m" "mac" "--mac-source" "${mc}") ;; esac # ipvall for ipv in ${ipvall} do case "${ipv}" in ipv4) iptables="iptables" src="${src4}" dst="${dst4}" ;; ipv6) iptables="ip6tables" src="${src6}" dst="${dst6}" ;; esac # src for s in ${src} do case ${s} in any|ANY) s_arg=() ;; ipset:*) s="${s/ipset:/}" test -z "${FIREHOL_IPSETS_USED[$s]}" && FIREHOL_IPSETS_USED[$s]="USED" s_arg=("-m" "set" "--match-set" "${s}" "src") ;; *) s_arg=("-s" "${s}") ;; esac # dst for d in ${dst} do case ${d} in any|ANY) d_arg=() ;; ipset:*) d="${d/ipset:/}" test -z "${FIREHOL_IPSETS_USED[$d]}" && FIREHOL_IPSETS_USED[$d]="USED" d_arg=("-m" "set" "--match-set" "${d}" "dst") ;; *) d_arg=("-d" "${d}") ;; esac # sport for sp in ${sport} do case ${sp} in any|ANY) sp_arg=() ;; *) sp_arg=("--sport" "${sp}") ;; esac # dport for dp in ${dport} do case ${dp} in any|ANY) dp_arg=() ;; *) dp_arg=("--dport" "${dp}") ;; esac # build the command basecmd=("${inf_arg[@]}" "${outf_arg[@]}" "${physdev_arg[@]}" "${inph_arg[@]}" "${outph_arg[@]}" "${limit_arg[@]}" "${iplimit_arg[@]}" "${proto_arg[@]}" "${s_arg[@]}" "${sp_arg[@]}" "${d_arg[@]}" "${dp_arg[@]}" "${owner_arg[@]}" "${uid_arg[@]}" "${gid_arg[@]}" "${pid_arg[@]}" "${sid_arg[@]}" "${cmd_arg[@]}" "${addrtype_arg[@]}" "${stp_arg[@]}" "${dtp_arg[@]}" "${state_arg[@]}" "${mc_arg[@]}" "${mark_arg[@]}" "${tos_arg[@]}" "${dscp_arg[@]}") if [ "$logrule" = "limit" ] then ${iptables} ${table} -A "${chain}" "${basecmd[@]}" ${custom} -m limit --limit "${FIREHOL_LOG_FREQUENCY}" --limit-burst "${FIREHOL_LOG_BURST}" -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}" elif [ "$logrule" = "normal" ] then ${iptables} ${table} -A "${chain}" "${basecmd[@]}" ${custom} -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}" fi if [ ! -z "${accounting}" ] then ${iptables} ${table} -A "${chain}" "${basecmd[@]}" ${custom} -m nfacct --nfacct-name "${accounting}" fi # do it! rule_action_param ${iptables} "${action}" "${pr}" "${statenot}" "${state}" "${table}" "${action_param[@]}" -- ${table} -A "${chain}" "${basecmd[@]}" ${custom} || failed=$[failed + 1] done # dport done # sport done # dst done # src done # ipvall done # mac done # physout done # physin done # outface done # inface done # proto done # dscp done # tos done # mark done # cmd done # sid done # pid done # gid done # uid test ${failed} -gt 0 && error "There are ${failed} failed commands." && return 1 return 0 } warning() { echo >&2 echo >&2 "WARNING: " "$@" echo >&2 return 0 } softwarning() { echo >&2 echo >&2 "--------------------------------------------------------------------------------" echo >&2 "WARNING" echo >&2 "WHEN : ${work_function}" echo >&2 "WHY :" "$@" printf >&2 "COMMAND: "; printf >&2 "%q " "${work_realcmd[@]}"; echo >&2 echo >&2 "MODE :" "${FIREHOL_NS_CURR}" echo >&2 "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 "${FIREHOL_MODE}" = "START" && syslog err "Error '${@}' when '${work_function}' $(config_line)" work_error=$[work_error + 1] echo >&2 echo >&2 "--------------------------------------------------------------------------------" echo >&2 "ERROR #: ${work_error}" echo >&2 "WHEN : ${work_function}" echo >&2 "WHY :" "$@" printf >&2 "COMMAND: "; printf >&2 "%q " "${work_realcmd[@]}"; echo >&2 echo >&2 "MODE :" "${FIREHOL_NS_CURR}" echo >&2 "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 "*** 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 chain="${1}" if running_ipv4; then test ! -z "${FIREHOL_CHAINS[${chain}.4]}" && return 1 # test -f "${FIREHOL_CHAINS_DIR}/${chain}.4" && return 1 fi if running_ipv6; then test ! -z "${FIREHOL_CHAINS[${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. create_chain() { local table="${1}" newchain="${2}" oldchain="${3}" shift 3 set_work_function "Creating chain '${newchain}' under '${oldchain}' in table '${table}'" chain_exists "${newchain}" test $? -eq 1 && error "Chain '${newchain}' already exists." && return 1 iptables_both -t ${table} -N "${newchain}" || return 1 if running_ipv4; then FIREHOL_CHAINS[${newchain}.4]="1" #${TOUCH_CMD} "${FIREHOL_CHAINS_DIR}/${newchain}.4" fi if running_ipv6; then FIREHOL_CHAINS[${newchain}.6]="1" #${TOUCH_CMD} "${FIREHOL_CHAINS_DIR}/${newchain}.6" fi if [ ! -z "${oldchain}" ] then rule table ${table} chain "${oldchain}" action "${newchain}" "$@" || return 1 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= # type = the current subcommand: server/client/route # services = the services to implement shift 2 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 filter "in_${mychain}" "in_${work_name}" || return 1 create_chain filter "out_${mychain}" "out_${work_name}" || return 1 # Try the simple services first simple_service "${mychain}" "${type}" "${service}" "$@" 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}" "$@" 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. simple_service() { local mychain="${1}" \ type="${2}" \ server="${3}" \ server_varname= server_ports= \ client_varname= client_ports= \ varname= helpers= \ x= value= shift 3 server_varname="server_${server}_ports" eval server_ports="\$${server_varname}" client_varname="client_${server}_ports" eval client_ports="\$${client_varname}" 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 return 127 fi varname="helper_${server}" eval helpers="\$${varname}" varname="require_${server}_modules" eval value="\$${varname}" for x in ${value} do require_kernel_module $x || return 1 done if [ ${FIREHOL_NAT} -eq 1 ] then varname="require_${server}_nat_modules" eval value="\$${varname}" for x in ${value} 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 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 } work_realcmd_primary() { config_line -ne work_realcmd=("$@") test ${FIREHOL_CONF_SHOW} -eq 1 && show_work_realcmd 1 } work_realcmd_secondary() { config_line -ne work_realcmd=("$@") test ${FIREHOL_CONF_SHOW} -eq 1 && show_work_realcmd 2 } work_realcmd_helper() { 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" \ -e "s/--pid-owner !/! --pid-owner /g" \ -e "s/--sid-owner !/! --sid-owner /g" \ -e "s/--cmd-owner !/! --cmd-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() { echo -n $"FireHOL: 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 $"FireHOL: Saving activated firewall to ${FIREHOL_SPOOL_DIR}:" echo 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 $"FireHOL: Saving activated firewall to ${FIREHOL_SPOOL_DIR}:" echo 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 $"FireHOL: Saving activated firewall to ${FIREHOL_SPOOL_DIR}:" echo 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 ipsets_apply spool || return 6 return 0 } firehol_restore_last_activated_firewall() { firehol_can_restore_saved_firewall || return 2 echo -n $"FireHOL: 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 $"FireHOL: 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 $"FireHOL: 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 $"FireHOL: Restoring last activated firewall from ${FIREHOL_SPOOL_DIR}:" return 3 fi fi success $"FireHOL: Saving activated firewall to ${FIREHOL_SPOOL_DIR}:" echo return 0 } # ------------------------------------------------------------------------------ # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # ------------------------------------------------------------------------------ # # START UP SCRIPT PROCESSING # # ------------------------------------------------------------------------------ # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # On non RedHat machines we need success() and failure() success() { printf " OK" } failure() { echo " FAILED" } # ------------------------------------------------------------------------------ # A small part bellow is copied from /etc/init.d/iptables # On RedHat systems this will define success() and failure() test -f /etc/init.d/functions && . /etc/init.d/functions 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 # ------------------------------------------------------------------------------ arg="${1}" shift if [ "${arg}" = "nofast" ] then FIREHOL_FAST_ACTIVATION=0 arg="${1}" shift fi 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" FIREHOL_CONF_SHOW=1 ;; 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." echo -n $"FireHOL: 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 $"FireHOL: Clearing Firewall:" echo 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 ;; status) test ! -z "${1}" && warning "Arguments after parameter '${arg}' are ignored." ( 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 ) | pager_cmd 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})" echo -n $"FireHOL: 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 $"FireHOL: Blocking all communications:" echo 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" FIREHOL_CONF_SHOW=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 "Cannot accept command line argument '${1}' here." exit 1 ;; esac else 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 -n $"FireHOL config ${FIREHOL_CONFIG} not found:" failure $"FireHOL config ${FIREHOL_CONFIG} not found:" echo exit 1 fi fi # ------------------------------------------------------------------------------ # 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} < " -e -r test -z "${REPLY}" && continue set_work_function -ne "Executing user input" while [ 1 = 1 ] do set -- ${REPLY} case "${1}" in help) ${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 ----------------------------------------------------------- echo -n $"FireHOL: Saving your running 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 $"FireHOL: Saving your running firewall to a temporary file:" echo else ${RM_CMD} -f "${FIREHOL_SAVED}" "${FIREHOL_SAVED6}" failure $"FireHOL: Saving your running firewall to a temporary file:" echo 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" 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_applied=0 firewall_policy() { firewall_policy_applied=1 iptables -t filter -P INPUT "${FIREHOL_INPUT_ACTIVATION_POLICY}" iptables -t filter -P OUTPUT "${FIREHOL_OUTPUT_ACTIVATION_POLICY}" iptables -t filter -P FORWARD "${FIREHOL_FORWARD_ACTIVATION_POLICY}" # Accept everything in/out the loopback device. if [ "${FIREHOL_TRUST_LOOPBACK}" = "1" ] then iptables -A INPUT -i lo -j ACCEPT iptables -A OUTPUT -o lo -j ACCEPT fi # Drop all invalid packets. # Netfilter HOWTO suggests to DROP all INVALID packets. if [ "${FIREHOL_DROP_INVALID}" = "1" ] then iptables -A INPUT -m conntrack --ctstate INVALID -j DROP iptables -A OUTPUT -m conntrack --ctstate INVALID -j DROP iptables -A FORWARD -m conntrack --ctstate INVALID -j DROP fi } firewall_policy6_applied=0 firewall_policy6() { firewall_policy6_applied=1 ip6tables -t filter -P INPUT "${FIREHOL_INPUT_ACTIVATION_POLICY}" ip6tables -t filter -P OUTPUT "${FIREHOL_OUTPUT_ACTIVATION_POLICY}" ip6tables -t filter -P FORWARD "${FIREHOL_FORWARD_ACTIVATION_POLICY}" # Accept everything in/out the loopback device. if [ "${FIREHOL_TRUST_LOOPBACK}" = "1" ] then ip6tables -A INPUT -i lo -j ACCEPT ip6tables -A OUTPUT -o lo -j ACCEPT fi # Drop all invalid packets. # Netfilter HOWTO suggests to DROP all INVALID packets. if [ "${FIREHOL_DROP_INVALID}" = "1" ] then ip6tables -A INPUT -m conntrack --ctstate INVALID -j DROP ip6tables -A OUTPUT -m conntrack --ctstate INVALID -j DROP ip6tables -A FORWARD -m conntrack --ctstate INVALID -j DROP fi } # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX echo -n $"FireHOL: Processing file ${FIREHOL_CONFIG}:" ret=0 # check if the user has given any iptables commands directly. if [ ! -z "`${CAT_CMD} ${FIREHOL_CONFIG} | ${EGREP_CMD} "(${IPTABLES_CMD}|${IP6TABLES_CMD})"`" ] 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 echo >&2 echo >&2 "NOTICE: No changes made to your firewall." failure $"FireHOL: Processing file ${FIREHOL_CONFIG}:" echo exit 1 fi success $"FireHOL: Processing file ${FIREHOL_CONFIG}:" echo # 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 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 ipsets_apply || exit 1 syslog info "Activating new firewall from ${FIREHOL_CONFIG} (translated to ${FIREHOL_COMMAND_COUNTER} iptables rules)." echo -n $"FireHOL: 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 ipsets_apply || exit 1 syslog info "Activating new firewall from ${FIREHOL_CONFIG} (translated to ${FIREHOL_COMMAND_COUNTER} iptables rules)." echo -n $"FireHOL: 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 $"FireHOL: Activating new firewall:" echo 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 $"FireHOL: Activating new firewall (${FIREHOL_COMMAND_COUNTER} rules):" echo syslog info "Activation of new firewall succeeded." 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 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 "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 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 exit 1 fi if [ $ENABLE_IPV4 -eq 1 ] then echo -n $"FireHOL: 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 $"FireHOL: Saving firewall to ${FIREHOL_AUTOSAVE}:" echo exit 1 fi syslog info "New firewall saved to '${FIREHOL_AUTOSAVE}'." success $"FireHOL: Saving firewall to ${FIREHOL_AUTOSAVE}:" echo fi if [ $ENABLE_IPV6 -eq 1 ] then echo -n $"FireHOL: 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 $"FireHOL: Saving IPv6 firewall to ${FIREHOL_AUTOSAVE6}:" echo exit 1 fi syslog info "New IPv6 firewall saved to '${FIREHOL_AUTOSAVE6}'." success $"FireHOL: Saving IPv6 firewall to ${FIREHOL_AUTOSAVE6}:" echo fi exit 0 fi