#!/bin/bash # # FireHOL - A firewall for humans... # # Copyright # # Copyright (C) 2003-2013 Costa Tsaousis # Copyright (C) 2012-2013 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. # 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) if [ "$FIREHOL_DEBUGGING" ]; then set -v; set -x; fi emit_version() { ${CAT_CMD} < (C) Copyright 2012-2013 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 fi # Remember who you are. FIREHOL_FILE="${0}" FIREHOL_DEFAULT_WORKING_DIRECTORY="${PWD}" # ------------------------------------------------------------------------------ # 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 block=1 if [ "a${1}" = "a-n" ] then local block=0 shift fi unalias $2 >/dev/null 2>&1 local 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 block=1 if [ "a$1" = "a-n" ] then local 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) # # wget or curl (either is fine) # 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 IPTABLES_CMD iptables which_cmd IPTABLES_SAVE_CMD iptables-save which_cmd IPTABLES_RESTORE_CMD iptables-restore which_cmd IP6TABLES_CMD ip6tables which_cmd IP6TABLES_SAVE_CMD ip6tables-save which_cmd IP6TABLES_RESTORE_CMD ip6tables-restore which_cmd LSMOD_CMD lsmod which_cmd MKDIR_CMD mkdir 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 # 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 } # Fetch a URL and output it to the standard output. wget_cmd() { local url="${1}" require_cmd wget curl if [ ! -z "${WGET_CMD}" ] then ${WGET_CMD} -O - "${url}" 2>/dev/null return $? elif [ ! -z "${CURL_CMD}" ] then ${CURL_CMD} -s "${url}" return $? fi error "Cannot use either 'wget' or 'curl' to fetch '${url}'." 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 "${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}" "${@}" } # Concurrent run control FIREHOL_LOCK_FILE="/var/run/firehol.lck" 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 ${IPTABLES_CMD} -nxvL >/dev/null 2>&1 ${IP6TABLES_CMD} -nxvL >/dev/null 2>&1 # ------------------------------------------------------------------------------ # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # ------------------------------------------------------------------------------ # # GLOBAL DEFAULTS # # ------------------------------------------------------------------------------ # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # ------------------------------------------------------------------------------ # ---------------------------------------------------------------------- # Directories and files # Create an empty temporary directory we need for this run. if ! FIREHOL_DIR="`mktemp -d -t 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" FIREHOL_TMP="${FIREHOL_DIR}/firehol-tmp.sh" # Redhat like status info for startup script FIREHOL_LOCK_DIR="/var/lock/subsys" test ! -d "${FIREHOL_LOCK_DIR}" && FIREHOL_LOCK_DIR="/var/lock" if [ -d "/var/spool" ] then FIREHOL_SPOOL_DIR="/var/spool/firehol" else FIREHOL_SPOOL_DIR="/tmp/firehol" fi # The default configuration file # It can be changed on the command line FIREHOL_CONFIG_DIR="/etc/firehol" FIREHOL_CONFIG="${FIREHOL_CONFIG_DIR}/firehol.conf" # ------------------------------------------------------------------------------ # 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 FIREHOL_SYSLOG_FACILITY="daemon" FIREHOL_NOTIFICATION_PROGRAM="" 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}" -a "${FIREHOL_MODE}" = "START" ] then echo echo -n $"FireHOL: Restoring old firewall:" ${IPTABLES_RESTORE_CMD} <"${FIREHOL_SAVED}" local status4=$? ${IP6TABLES_RESTORE_CMD} <"${FIREHOL_SAVED6}" local status6=$? if [ $status4 -eq 0 -a $status6 -eq 0 ] then local restored="OK" success $"FireHOL: Restoring old firewall:" else local 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}." local result="FAILED" else syslog info "Successfully activated new firewall from ${FIREHOL_CONFIG}." local result="OK" fi local notify=1 ;; STOP) syslog emerg "Firewall has been stopped. Policy is ACCEPT EVERYTHING!" local notify=1 ;; PANIC) syslog emerg "PANIC! Machine has been locked. Policy is DROP EVERYTHING!" local notify=1 ;; *) # do nothing for the rest local 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 &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_CONFIG_DIR}/services/" if [ ! -d "${FIREHOL_CONFIG_DIR}/services" ] then "${MKDIR_CMD}" "${FIREHOL_CONFIG_DIR}/services" if [ $? -ne 0 ] then echo >&2 echo >&2 echo >&2 "FireHOL needs to create the directory '${FIREHOL_CONFIG_DIR}/services', 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_CONFIG_DIR}/services" "${CHMOD_CMD}" 700 "${FIREHOL_CONFIG_DIR}/services" fi # Remove any old directories that might be there. if [ -d "${FIREHOL_DIR}" ] then "${RM_CMD}" -rf "${FIREHOL_DIR}" if [ $? -ne 0 -o -e "${FIREHOL_DIR}" ] then echo >&2 echo >&2 echo >&2 "Cannot clean temporary directory '${FIREHOL_DIR}'." echo >&2 exit 1 fi fi "${MKDIR_CMD}" "${FIREHOL_DIR}" || exit 1 "${MKDIR_CMD}" "${FIREHOL_CHAINS_DIR}" || exit 1 "${MKDIR_CMD}" "${FIREHOL_DIR}/fast" || exit 1 "${MKDIR_CMD}" "${FIREHOL_DIR}/fast/tables" || 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. cat >"${FIREHOL_DIR}/modules_to_load.sh" <&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}/${v}" -mtime +${dt}` if [ ! -z "${t}" ] then echo >&2 echo >&2 echo >&2 "WARNING" echo >&2 "File '${FIREHOL_CONFIG_DIR}/${v}' 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}/${v}" | ${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}/${v}" | ${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}/${v}' 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 # Suggested by Fco.Felix Belmonte # Optimized (CIDR) by Marc 'HE' Brockschmidt # Further optimized and reduced by http://www.vergenet.net/linux/aggregate/ # The supplied get-iana.sh uses 'aggregate-flim' if it finds it in the path. RESERVED_IPS="0.0.0.0/8 127.0.0.0/8 240.0.0.0/4 " #load_ips RESERVED_IPS "${RESERVED_IPS}" 90 "Run the supplied get-iana.sh script to generate this file." require-file load_ips RESERVED_IPS "${RESERVED_IPS}" 0 # Private IPv4 address space # Suggested by Fco.Felix Belmonte # Revised by me according to RFC 3330. Explanation: # 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_IPS="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" load_ips PRIVATE_IPS "${PRIVATE_IPS}" 0 # The multicast address space MULTICAST_IPS="224.0.0.0/4" load_ips MULTICAST_IPS "${MULTICAST_IPS}" 0 # A shortcut to have all the Internet unroutable addresses in one # variable UNROUTABLE_IPS="${RESERVED_IPS} ${PRIVATE_IPS}" load_ips UNROUTABLE_IPS "${UNROUTABLE_IPS}" 0 # ---------------------------------------------------------------------- # Runtime control variables # These do not affect the final firewall output and will honour the # environment variable of the same name if it is set. They can also # be set in the configuration file. # Which is the filter table chains policy during firewall activation? test -z "$FIREHOL_INPUT_ACTIVATION_POLICY" && \ FIREHOL_INPUT_ACTIVATION_POLICY="ACCEPT" test -z "$FIREHOL_OUTPUT_ACTIVATION_POLICY" && \ FIREHOL_OUTPUT_ACTIVATION_POLICY="ACCEPT" test -z "$FIREHOL_FORWARD_ACTIVATION_POLICY" && \ FIREHOL_FORWARD_ACTIVATION_POLICY="ACCEPT" # Do we allow pre-existing connections to continue during activation? test -z "$FIREHOL_ESTABLISHED_ACTIVATION_ACCEPT" && \ FIREHOL_ESTABLISHED_ACTIVATION_ACCEPT=1 # If set to 0, firehol will not try to load the required kernel modules test -z "$FIREHOL_LOAD_KERNEL_MODULES" && \ FIREHOL_LOAD_KERNEL_MODULES=1 # Set this to 1 have firehol load NAT kernel modules # It will generally be set automatically at an appropriate time test -z "$FIREHOL_NAT" && \ FIREHOL_NAT=0 # Set this to 1 routing should be enabled in the kernel # It will generally be set automatically at an appropriate time test -z "$FIREHOL_ROUTING" && \ FIREHOL_ROUTING=0 # Where /etc/init.d/iptables expects its configuration? # Leave it empty for automatic detection test -z "$FIREHOL_AUTOSAVE" && \ FIREHOL_AUTOSAVE= # Where /etc/init.d/ip6tables expects its configuration? # Leave it empty for automatic detection test -z "$FIREHOL_AUTOSAVE6" && \ FIREHOL_AUTOSAVE6= # Set to non-empty to wait (max 60 seconds) for a network interface test -z "$WAIT_FOR_IFACE" && \ WAIT_FOR_IFACE= # If set to 1, FireHOL will attempt to load the firewall with # iptables-restore. This is beta. test -z "$FIREHOL_FAST_ACTIVATION" && \ FIREHOL_FAST_ACTIVATION=0 test -z "$FIREHOL_DEFAULT_NAMESPACE" && \ FIREHOL_DEFAULT_NAMESPACE=both # ---------------------------------------------------------------------- # Firewall configuration variables # These affect the final output firewall. They can be set in the # configuration file. # The default policy for the interface commands of the firewall. # This can be controlled on a per interface basis using the # policy interface subscommand. 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_ROUTER_POLICY="RETURN" # Should we drop all INVALID packets always? FIREHOL_DROP_INVALID=0 # What to do with unmatched packets? # To change these, simply define them the configuration file. UNMATCHED_INPUT_POLICY="DROP" UNMATCHED_OUTPUT_POLICY="DROP" UNMATCHED_ROUTER_POLICY="DROP" # Options for iptables LOG action. # These options will be added to all LOG actions FireHOL will generate. # To change them, type such a line in the configuration file. # FIREHOL_LOG_OPTIONS="--log-tcp-sequence --log-tcp-options --log-ip-options" FIREHOL_LOG_OPTIONS="" FIREHOL_LOG_LEVEL="warning" FIREHOL_LOG_MODE="LOG" FIREHOL_LOG_FREQUENCY="1/second" FIREHOL_LOG_BURST="5" FIREHOL_LOG_PREFIX="" # If enabled, FireHOL will silently drop orphan TCP packets with ACK,FIN set. FIREHOL_DROP_ORPHAN_TCP_ACK_FIN=0 # The client ports to be used for "default" client ports when the # client specified is a foreign host. # We give all ports above 1000 because a few systems (like Solaris) # use this range. # Note that FireHOL will ask the kernel for default client ports of # the local host. This only applies to client ports of remote hosts. DEFAULT_CLIENT_PORTS="1024:65535" # 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=5 # ---------------------------------------------------------------------- # The initial line number of the configuration file. FIREHOL_LINEID="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. 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=1 # ------------------------------------------------------------------------------ # Keep information about the current namespace: ipv4, ipv6 or both FIREHOL_NS_STACK= FIREHOL_NS_CURR=$FIREHOL_DEFAULT_NAMESPACE FIREHOL_NS_PREP= peek_namespace() { local ns=$(echo $FIREHOL_NS_STACK | cut -f1 -d:) if [ "$ns" = "" ] then echo $FIREHOL_DEFAULT_NAMESPACE else echo $ns fi } 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=$(peek_namespace) return 0 } pop_namespace() { FIREHOL_NS_STACK=$(echo $FIREHOL_NS_STACK | cut -f2- -d:) FIREHOL_NS_CURR=$(peek_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=both $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 } # If set to 0, FireHOL will not trust interface lo for all traffic. # This means the admin could setup a firewall on lo. FIREHOL_TRUST_LOOPBACK=1 # 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. # --- 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 mychain="${1}"; shift local type="${1}"; shift local in=in local out=out if [ "${type}" = "client" ] then in=out out=in fi local client_ports="${DEFAULT_CLIENT_PORTS}" 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 mychain="${1}"; shift local type="${1}"; shift local in=in local out=out if [ "${type}" = "client" ] then in=out out=in fi local client_ports="${DEFAULT_CLIENT_PORTS}" 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 return 0 } # --- EMULE -------------------------------------------------------------------- rules_emule() { local mychain="${1}"; shift local type="${1}"; shift local in=in local out=out if [ "${type}" = "client" ] then in=out out=in fi local client_ports="${DEFAULT_CLIENT_PORTS}" 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 mychain="${1}"; shift local type="${1}"; shift local in=in local out=out if [ "${type}" = "client" ] then in=out out=in fi local client_ports="${DEFAULT_CLIENT_PORTS}" 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 mychain="${1}"; shift local type="${1}"; shift local in=in local out=out if [ "${type}" = "client" ] then in=out out=in fi local client_ports="${DEFAULT_CLIENT_PORTS}" 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 } # --- PPTP -------------------------------------------------------------------- # THIS IS OLD # THE NEW ONE USES THE KERNEL HELPER TO DO IT, AND THEREFORE IS A SIMPLE SERVICE # #rules_pptp() { # local mychain="${1}"; shift # local type="${1}"; shift # # local in=in # local out=out # if [ "${type}" = "client" ] # then # in=out # out=in # fi # # local client_ports="${DEFAULT_CLIENT_PORTS}" # if [ "${type}" = "client" -a "${work_cmd}" = "interface" ] # then # client_ports="${LOCAL_CLIENT_PORTS}" # fi # # # ---------------------------------------------------------------------- # # set_work_function "Setting up rules for PPTP/initial connection (${type})" # rule ${in} action "$@" chain "${in}_${mychain}" proto "tcp" sport "${client_ports}" dport "1723" state NEW,ESTABLISHED || return 1 # rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "tcp" sport "${client_ports}" dport "1723" state ESTABLISHED || return 1 # # set_work_function "Setting up rules for PPTP/tunnel GRE traffic (${type})" # rule ${in} action "$@" chain "${in}_${mychain}" proto "47" || return 1 # rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "47" || return 1 # # return 0 #} # --- NFS ---------------------------------------------------------------------- rules_nfs() { local mychain="${1}"; shift local type="${1}"; shift local in=in local out=out if [ "${type}" = "client" ] then in=out out=in fi local client_ports="${DEFAULT_CLIENT_PORTS}" 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 action="${1}"; shift local servers="localhost" if [ "${type}" = "client" -o ! "${work_cmd}" = "interface" ] then case "${1}" in dst|DST|destination|DESTINATION) shift local 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="${FIREHOL_DIR}/firehol.rpcinfo.$$.${RANDOM}" 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 mychain="${1}"; shift local type="${1}"; shift local in=in local out=out if [ "${type}" = "client" ] then in=out out=in fi local client_ports="${DEFAULT_CLIENT_PORTS}" 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 action="${1}"; shift local servers="localhost" if [ "${type}" = "client" -o ! "${work_cmd}" = "interface" ] then case "${1}" in dst|DST|destination|DESTINATION) shift local 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="${FIREHOL_DIR}/firehol.rpcinfo.$$.${RANDOM}" 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 } # --- AMANDA ------------------------------------------------------------------- # THIS IS OLD # THE NEW ONE USES THE KERNEL HELPER TO DO IT, AND THEREFORE IS A SIMPLE SERVICE # #FIREHOL_AMANDA_PORTS="850:859" # #rules_amanda() { # local mychain="${1}"; shift # local type="${1}"; shift # # local in=in # local out=out # if [ "${type}" = "client" ] # then # in=out # out=in # fi # # local client_ports="${DEFAULT_CLIENT_PORTS}" # if [ "${type}" = "client" -a "${work_cmd}" = "interface" ] # then # client_ports="${LOCAL_CLIENT_PORTS}" # fi # # # ---------------------------------------------------------------------- # # set_work_function "*** AMANDA: See http://amanda.sourceforge.net/fom-serve/cache/139.html" # # # set_work_function "Setting up rules for initial amanda server-to-client connection" # rule ${out} action "$@" chain "${out}_${mychain}" proto "udp" dport 10080 state NEW,ESTABLISHED || return 1 # rule ${in} reverse action "$@" chain "${in}_${mychain}" proto "udp" dport 10080 state ESTABLISHED || return 1 # # # set_work_function "Setting up rules for amanda data exchange client-to-server" # rule ${in} action "$@" chain "${in}_${mychain}" proto "tcp udp" dport "${FIREHOL_AMANDA_PORTS}" state NEW,ESTABLISHED || return 1 # rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "tcp udp" dport "${FIREHOL_AMANDA_PORTS}" state ESTABLISHED || return 1 # # return 0 #} # --- FTP ---------------------------------------------------------------------- # THIS IS OLD # THE NEW ONE USES THE KERNEL HELPER TO DO IT, AND THEREFORE IS A SIMPLE SERVICE # #ALL_SHOULD_ALSO_RUN="${ALL_SHOULD_ALSO_RUN} ftp" # #rules_ftp() { # local mychain="${1}"; shift # local type="${1}"; shift # # local in=in # local out=out # if [ "${type}" = "client" ] # then # in=out # out=in # fi # # local client_ports="${DEFAULT_CLIENT_PORTS}" # if [ "${type}" = "client" -a "${work_cmd}" = "interface" ] # then # client_ports="${LOCAL_CLIENT_PORTS}" # fi # # # For an explanation of how FTP connections work, see # # http://slacksite.com/other/ftp.html # # # ---------------------------------------------------------------------- # # # allow new and established incoming, and established outgoing # # accept port ftp new connections # set_work_function "Setting up rules for initial FTP connection ${type}" # rule ${in} action "$@" chain "${in}_${mychain}" proto tcp sport "${client_ports}" dport ftp state NEW,ESTABLISHED || return 1 # rule ${out} reverse action "$@" chain "${out}_${mychain}" proto tcp sport "${client_ports}" dport ftp state ESTABLISHED || return 1 # # set_work_function "Match anything related to the kernel ftp helper" # rule ${in} action "$@" chain "${in}_${mychain}" custom "-m helper --helper ftp" state ESTABLISHED,RELATED || return 1 # rule ${out} reverse action "$@" chain "${out}_${mychain}" custom "-m helper --helper ftp" state ESTABLISHED,RELATED || return 1 # # this is old code - replaced by the two helper statements above # # Active FTP # # send port ftp-data related connections # set_work_function "Setting up rules for Active FTP ${type}" # rule ${out} reverse action "$@" chain "${out}_${mychain}" proto tcp sport "${client_ports}" dport ftp-data state ESTABLISHED,RELATED || return 1 # rule ${in} action "$@" chain "${in}_${mychain}" proto tcp sport "${client_ports}" dport ftp-data state ESTABLISHED || return 1 # # # ---------------------------------------------------------------------- # # # A hack for Passive FTP only # local s_client_ports="${DEFAULT_CLIENT_PORTS}" # local c_client_ports="${DEFAULT_CLIENT_PORTS}" # # if [ "${type}" = "client" -a "${work_cmd}" = "interface" ] # then # c_client_ports="${LOCAL_CLIENT_PORTS}" # elif [ "${type}" = "server" -a "${work_cmd}" = "interface" ] # then # s_client_ports="${LOCAL_CLIENT_PORTS}" # fi # # # Passive FTP # # accept high-ports related connections # set_work_function "Setting up rules for Passive FTP ${type}" # rule ${in} action "$@" chain "${in}_${mychain}" proto tcp sport "${c_client_ports}" dport "${s_client_ports}" state ESTABLISHED,RELATED || return 1 # rule ${out} reverse action "$@" chain "${out}_${mychain}" proto tcp sport "${c_client_ports}" dport "${s_client_ports}" state ESTABLISHED || return 1 # # require_kernel_module nf_conntrack_ftp # test ${FIREHOL_NAT} -eq 1 && require_kernel_module nf_nat_ftp # # return 0 #} # --- TFTP --------------------------------------------------------------------- # THIS IS OLD # THE NEW ONE USES THE KERNEL HELPER TO DO IT, AND THEREFORE IS A SIMPLE SERVICE # # Written by: Goetz Bock # #rules_tftp() { # local mychain="${1}"; shift # local type="${1}"; shift # # local in=in # local out=out # if [ "${type}" = "client" ] # then # in=out # out=in # fi # # local client_ports="${DEFAULT_CLIENT_PORTS}" # if [ "${type}" = "client" -a "${work_cmd}" = "interface" ] # then # client_ports="${LOCAL_CLIENT_PORTS}" # fi # # # --------------------------------------------------------------------- # # TFTP is a broken protokol. It works like this: # # # # 1. The client sends from a high port (a) to the server's tftp port an # # udp packet with "give me file 'bla'". # # # # 2. The server replies from a high port (b) to the highport the client # # used (a) with "this is part 0 if your file" # # # # 3. The client now has to send a reply (from his highport a) to the # # servers high port (b): "got part 0, send next part 1". # # # # 4. repeat 2. and 3. till file transmitted # # # allow the initial TFTP connection # set_work_function "Setting up rules for initial TFTP connection (${type})" # rule ${in} action "$@" chain "${in}_${mychain}" proto "udp" sport "${client_ports}" dport tftp state NEW,ESTABLISHED || return 1 ## rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "udp" sport "${client_ports}" dport tftp state ESTABLISHED || return 1 # # # We now need both server and client port ranges # local s_client_ports="${DEFAULT_CLIENT_PORTS}" # local c_client_ports="${DEFAULT_CLIENT_PORTS}" # # if [ "${type}" = "client" -a "${work_cmd}" = "interface" ] # then # c_client_ports="${LOCAL_CLIENT_PORTS}" # elif [ "${type}" = "server" -a "${work_cmd}" = "interface" ] # then # s_client_ports="${LOCAL_CLIENT_PORTS}" # fi # # # allow the TFTP server to establish a new connection to the client # set_work_function "Setting up rules for server-to-client TFTP connection (${type})" # rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "udp" sport "${c_client_ports}" dport "${s_client_ports}" state RELATED,ESTABLISHED || return 1 # rule ${in} action "$@" chain "${in}_${mychain}" proto "udp" sport "${c_client_ports}" dport "${s_client_ports}" state ESTABLISHED || return 1 # # require_kernel_module nf_conntrack_tftp # test ${FIREHOL_NAT} -eq 1 && require_kernel_module nf_nat_tftp # # return 0 #} # --- PING --------------------------------------------------------------------- rules_ping() { local mychain="${1}"; shift local type="${1}"; shift local in=in local out=out if [ "${type}" = "client" ] then in=out out=in fi local client_ports="${DEFAULT_CLIENT_PORTS}" if [ "${type}" = "client" -a "${work_cmd}" = "interface" ] then client_ports="${LOCAL_CLIENT_PORTS}" fi # ---------------------------------------------------------------------- # allow incoming new and established PING packets rule ${in} action "$@" chain "${in}_${mychain}" proto icmp custom "--icmp-type echo-request" state NEW,ESTABLISHED || return 1 # allow outgoing established packets rule ${out} reverse action "$@" chain "${out}_${mychain}" proto icmp custom "--icmp-type echo-reply" state ESTABLISHED || return 1 return 0 } # --- TIMESTAMP ---------------------------------------------------------------- rules_timestamp() { local mychain="${1}"; shift local type="${1}"; shift local in=in local out=out if [ "${type}" = "client" ] then in=out out=in fi local client_ports="${DEFAULT_CLIENT_PORTS}" if [ "${type}" = "client" -a "${work_cmd}" = "interface" ] then client_ports="${LOCAL_CLIENT_PORTS}" fi # ---------------------------------------------------------------------- # allow incoming new and established PING packets rule ${in} action "$@" chain "${in}_${mychain}" proto icmp custom "--icmp-type timestamp-request" state NEW,ESTABLISHED || return 1 # allow outgoing established packets rule ${out} reverse action "$@" chain "${out}_${mychain}" proto icmp custom "--icmp-type timestamp-reply" state ESTABLISHED || return 1 return 0 } # --- ALL ---------------------------------------------------------------------- rules_all() { local mychain="${1}"; shift local type="${1}"; shift local in=in local out=out if [ "${type}" = "client" ] then in=out out=in fi local client_ports="${DEFAULT_CLIENT_PORTS}" 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 mychain="${1}"; shift local type="${1}"; shift local name="${1}"; shift # a special case: service any gets a name local in=in local out=out if [ "${type}" = "client" ] then in=out out=in fi local client_ports="${DEFAULT_CLIENT_PORTS}" 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 mychain="${1}"; shift local type="${1}"; shift local name="${1}"; shift # a special case: service any gets a name local in=in local out=out if [ "${type}" = "client" ] then in=out out=in fi local client_ports="${DEFAULT_CLIENT_PORTS}" 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 mychain="${1}"; shift local type="${1}"; shift local in=in local out=out if [ "${type}" = "client" ] then in=out out=in fi local client_ports="${DEFAULT_CLIENT_PORTS}" 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 mychain="${1}"; shift local type="${1}"; shift local server="${1}"; shift local my_server_ports="${1}"; shift local my_client_ports="${1}"; shift local helpers= if [ "$1" = "helpers" ] then local helpers="$2" shift 2 fi local in=in local out=out if [ "${type}" = "client" ] then in=out out=in fi local client_ports="${DEFAULT_CLIENT_PORTS}" if [ "${type}" = "client" -a "${work_cmd}" = "interface" ] then client_ports="${LOCAL_CLIENT_PORTS}" fi # ---------------------------------------------------------------------- local sp= for sp in ${my_server_ports} do local proto= local sport= IFS="/" read proto sport </dev/null` do cd "${FIREHOL_CONFIG_DIR}/services" || exit 1 if [ ! -O "${f}" ] then echo >&2 " >>> Ignoring service in '${FIREHOL_CONFIG_DIR}/services/${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_CONFIG_DIR}/services/${f}' due to malformed header." elif [ ${n} -ne ${FIREHOL_SERVICES_API} ] then echo >&2 " >>> Ignoring service '${FIREHOL_CONFIG_DIR}/services/${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_CONFIG_DIR}/services/${f}' due to malformed API minor number." else source ${f} ret=$? if [ ${ret} -ne 0 ] then echo >&2 " >>> Service in '${FIREHOL_CONFIG_DIR}/services/${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() { softwarning "ECN_SHAME IP list no longer available, helper is ignored." return 0 } # Fetch a URL and output it to the standard output. firehol_wget() { local url="${1}" require_cmd wget curl if [ ! -z "${WGET_CMD}" ] then ${WGET_CMD} -O - "${url}" 2>/dev/null return $? elif [ ! -z "${CURL_CMD}" ] then ${CURL_CMD} -s "${url}" return $? fi error "Cannot use either 'wget' or 'curl' to fetch '${url}'." return 1 } # 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}" && local 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}"; shift local redirect="${1}"; shift local user="${1}"; shift 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 "$@" } 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}"; shift local to="${1}"; shift 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 local action=snat ;; to-destination) create_chain nat "nat.${nat_count}" PREROUTING noowner nolog "$@" outface any || return 1 local action=dnat ;; redirect-to) create_chain nat "nat.${nat_count}" PREROUTING noowner nolog "$@" outface any || return 1 local 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" && local 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" && local 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" && local 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 iptables_both -A BL_OUT_BI -m conntrack --ctstate NEW -j REJECT --reject-with icmp-host-unreachable iptables_both -A BL_OUT_BI -j REJECT blacklist_chain=1 fi set_work_function "Generating blacklist rules" local z= for z in $@ do local x= for x in ${z} do set_work_function "Blacklisting '${x}'" 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 done 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_count=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}"; shift local where="${1}"; shift test -z "${where}" && where=OUTPUT connmark_count=$[connmark_count + 1] set_work_function "Setting up rules for CONNMARK" create_chain mangle "connmark.${connmark_count}" "${where}" "$@" || return 1 case "${num}" in save) iptables_both -t mangle -A "connmark.${connmark_count}" -j CONNMARK --save-mark ;; restore) iptables_both -t mangle -A "connmark.${connmark_count}" -j CONNMARK --restore-mark ;; *) iptables_both -t mangle -A "connmark.${connmark_count}" -j CONNMARK --set-mark ${num} ;; esac return 0 } mark_count=0 mark() { 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}"; shift local where="${1}"; shift test -z "${where}" && where=OUTPUT mark_count=$[mark_count + 1] set_work_function "Setting up rules for MARK" create_chain mangle "mark.${mark_count}" "${where}" "$@" || return 1 iptables_both -t mangle -A "mark.${mark_count}" -j MARK --set-mark ${num} return 0 } 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}"; shift local where="${1}"; shift 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=($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=($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}"; shift local class="" if [ "${value}" = "class" ] then local value="" local class="${1}"; shift fi local 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" local iface="$2" if [ -z "$iface" ] then if [ ! -z "${work_cmd}" ] then local iface="${work_outface}" if [ -z "$iface" ] then error "$FUNCNAME cannot find the interfaces to setup. Did you set an outface?" return 1 fi else local iface="all" fi fi local target= case "$value" in auto) local target="-j TCPMSS --clamp-mss-to-pmtu" ;; [0-9]*) local 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 } # ------------------------------------------------------------------------------ # 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 release number (R5 currently). version() { work_realcmd_helper ${FUNCNAME} "$@" if [ ${1} -gt ${FIREHOL_VERSION} ] then error "Wrong version. FireHOL is v${FIREHOL_VERSION}, your script requires v${1}." 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}"; shift test -z "${inface}" && error "real interface is not set" && return 1 # Get the name for this interface 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}" 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 } router() { work_realcmd_helper ${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 } postprocess() { # work_realcmd_helper ${FUNCNAME} "$@" local check="error" test "A${1}" = "A-ne" && shift && local check="none" test "A${1}" = "A-warn" && shift && local check="warn" test "${FIREHOL_MODE}" = "DEBUG" && local check="none" test "${FIREHOL_MODE}" = "EXPLAIN" && local check="none" if [ ! ${check} = "none" ] then printf "runcmd '${check}' '${FIREHOL_LINEID}' " >>${FIREHOL_OUTPUT} fi printf "%q " "$@" >>${FIREHOL_OUTPUT} printf "\n" >>${FIREHOL_OUTPUT} if [ "${FIREHOL_MODE}" = "EXPLAIN" ] then ${CAT_CMD} ${FIREHOL_OUTPUT} ${RM_CMD} -f ${FIREHOL_OUTPUT} fi return 0 } runcmd() { local check="${1}"; shift local line="${1}"; shift local cmd="${1}"; shift "${cmd}" "$@" >${FIREHOL_OUTPUT}.log 2>&1 local r=$? if [ "$FIREHOL_DEBUGGING" ] then "${CAT_CMD}" ${FIREHOL_OUTPUT}.log 1>&2 fi test ${r} -gt 0 && runtime_error ${check} ${r} ${line} "${cmd}" "$@" 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. iptables_fast() { local t=filter if [ "z$1" = "z-t" ] then local t="$2" shift 2 fi case "$1" in -P) echo ":$2 $3 [0:0]" >>$FIREHOL_DIR/fast/table.${t}.policy ;; -N) echo ":$2 - [0:0]" >>$FIREHOL_DIR/fast/table.${t}.chains ;; -A) ${CAT_CMD} <>$FIREHOL_DIR/fast/table.${t}.rules ${@} EOFA ;; -I) ${CAT_CMD} <>$FIREHOL_DIR/fast/table.${t}.rules ${@} EOFI ;; # if it is none of the above, we execute it normally. *) echo "Ignoring command 'iptables -t ${t} ${@}'" ;; esac test ! -f $FIREHOL_DIR/fast/tables/${t} && ${TOUCH_CMD} $FIREHOL_DIR/fast/tables/${t} return 0 } FIREHOL_COMMAND_COUNTER=0 iptables() { [ $firewall_policy_applied -eq 0 ] && firewall_policy if [ $FIREHOL_FAST_ACTIVATION -eq 1 ] then iptables_fast "${@}" else postprocess "${IPTABLES_CMD}" "$@" FIREHOL_COMMAND_COUNTER=$[FIREHOL_COMMAND_COUNTER + 1] fi return 0 } FIREHOL_COMMAND6_COUNTER=0 ip6tables() { [ $firewall_policy6_applied -eq 0 ] && firewall_policy6 if [ $FIREHOL_FAST_ACTIVATION -eq 1 ] then ip6tables_fast "${@}" else postprocess "${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 $? } client() { work_realcmd_secondary ${FUNCNAME} "$@" require_work set any || return 1 smart_function client "$@" return $? } route() { work_realcmd_secondary ${FUNCNAME} "$@" require_work set router || return 1 smart_function server "$@" return $? } # --- protection --------------------------------------------------------------- protection() { work_realcmd_secondary ${FUNCNAME} "$@" require_work set any || return 1 local in="in" local prface="${work_inface}" local pre="pr" local reverse= if [ "${1}" = "reverse" ] then local reverse="reverse" # needed to recursion local pre="prr" # in case a router has protections # both ways, the second needs to # have different chain names local in="out" # reverse the interface prface="${work_outface}" shift fi local type="${1}" local rate="${2}" local 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}'" local x= 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) local mychain="${pre}_${work_name}_fragments" create_chain filter "${mychain}" "${in}_${work_name}" in custom "-f" || return 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 || return 1 ;; 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" create_chain filter "${mychain}" "${in}_${work_name}" in proto icmp custom "--icmp-type echo-request" || return 1 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= 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 # 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 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 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. load_kernel_module() { local mod="${1}" if [ ! ${FIREHOL_LOAD_KERNEL_MODULES} -eq 0 ] then check_kernel_module ${mod} if [ $? -gt 0 ] then runcmd warn ${FIREHOL_LINEID} modprobe_cmd ${mod} -q echo >>"${FIREHOL_DIR}/modules_to_load.sh" "${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() { local new="${1}" local m= for m in ${FIREHOL_KERNEL_MODULES} do test "${m}" = "${new}" && return 0 done set_work_function "Adding kernel module '${new}' in the list of kernel modules to load" FIREHOL_KERNEL_MODULES="${FIREHOL_KERNEL_MODULES} ${new}" return 0 } # ------------------------------------------------------------------------------ # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # ------------------------------------------------------------------------------ # # INTERNAL FUNCTIONS BELLOW THIS POINT - FireHOL internals # # ------------------------------------------------------------------------------ # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # ------------------------------------------------------------------------------ set_work_function() { local show_explain=1 test "$1" = "-ne" && shift && local 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" "$*" >>${FIREHOL_OUTPUT} 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}" local 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" # 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) # 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]} ;; *) error "Statement 'group' requires the first argument to be one of with, start, begin, end, stop, close." return 1 ;; esac return 0 } close_all_groups() { while [ ${FIREHOL_GROUP_DEPTH} -gt 0 ] do group close || return 1 done return 0 } # ------------------------------------------------------------------------------ # 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}"; shift local action="${1}"; shift local protocol="${1}"; shift local statenot="${1}"; shift local state="${1}"; shift local table="${1}"; shift local -a action_param=() # All arguments until the separator are the parameters of the action local count=0 while [ ! -z "${1}" -a ! "A${1}" = "A--" ] do action_param[$count]="${1}" shift count=$[count + 1] done # If we don't have a seperator, generate an error local sep="${1}"; shift if [ ! "A${sep}" = "A--" ] then error "Internal Error, in parsing action_param parameters ($FUNCNAME '${action}' '${protocol}' '${statenot}' '${state}' '${table}' '${action_param[@]}' ${sep} '$@')." 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]}" local burst="${action_param[2]}" local overflow="REJECT" # if we have a custom overflow action, parse it. test "${action_param[3]}" = "overflow" && local 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. local -a 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}" && local do_accept_limit=1 else test -z "${has_new}" && local 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 "ACCEPT LIMIT ${freq} ${burst} ${overflow}" | tr " /." "___"`" # does the chain we need already exist? if [ ! -f "${FIREHOL_CHAINS_DIR}/${accept_limit_chain}" ] then # the chain does not exist. create it. $iptables_cmd ${table} -N "${accept_limit_chain}" touch "${FIREHOL_CHAINS_DIR}/${accept_limit_chain}" # 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 local -a logopts_arg=("--ulog-prefix=${FIREHOL_LOG_PREFIX}LIMIT_OVERFLOW:") elif [ "${FIREHOL_LOG_MODE}" = "NFLOG" ] then local -a logopts_arg=("--nflog-prefix=${FIREHOL_LOG_PREFIX}LIMIT_OVERFLOW:") else local -a 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 local action=${accept_limit_chain} fi ;; "recent") # limit NEW connections to the specified rate local name="${action_param[1]}" local seconds="${action_param[2]}" local 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. local -a 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}" && local do_accept_recent=1 else test -z "${has_new}" && local 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 "ACCEPT RECENT $name $seconds $hits" | tr " /." "___"`" # does the chain we need already exist? if [ ! -f "${FIREHOL_CHAINS_DIR}/${accept_recent_chain}" ] then # the chain does not exist. create it. $iptables_cmd ${table} -N "${accept_recent_chain}" touch "${FIREHOL_CHAINS_DIR}/${accept_recent_chain}" # 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= test ! -z $seconds && local t1="--seconds ${seconds}" local t2= test ! -z $hits && local 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 local 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. local -a action_param=() # does the knock chain exists? if [ ! -f "${FIREHOL_CHAINS_DIR}/${name}" ] then # the chain does not exist. create it. $iptables_cmd ${table} -N "${name}" touch "${FIREHOL_CHAINS_DIR}/${name}" $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 local 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[@]}" local ret=$? test $ret -gt 0 && failed=$[failed + 1] return $ret } rule() { local table= local chain= local inface=any local infacenot= local outface=any local outfacenot= local physin=any local physinnot= local physout=any local physoutnot= local mac=any local macnot= local src4=default local src4not= local dst4=default local dst4not= local src6=default local src6not= local dst6=default local dst6not= local srctype= local srctypenot= local dsttype= local dsttypenot= local sport=any local sportnot= local dport=any local dportnot= local proto=any local protonot= local uid=any local uidnot= local gid=any local gidnot= local pid=any local pidnot= local sid=any local sidnot= local cmd=any local cmdnot= local mark=any local marknot= local dscp=any local dscptype= local despnot= local tos=any local tosnot= local log= local logtxt= local loglevel= local limit= local burst= local iplimit= local iplimit_mask= local action= local state= local statenot= local failed=0 local reverse=0 local swi=0 local swo=0 local custom= # 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 if running_ipv4; then src4not= fi if running_ipv6; then src6not= fi if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift if running_ipv4; then src4not="!" fi if running_ipv6; then src6not="!" fi 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 if running_ipv4; then dst4not= fi if running_ipv6; then dst6not= fi if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift if running_ipv4; then dst4not="!" fi if running_ipv6; then dst6not="!" fi 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) 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 if running_ipv4; then dst4not= fi if running_ipv6; then dst6not= fi if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift if running_ipv4; then dst4not="!" fi if running_ipv6; then dst6not="!" fi 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 if running_ipv4; then src4not= fi if running_ipv6; then src6not= fi if [ "${1}" = "not" -o "${1}" = "NOT" ] then shift if running_ipv4; then src4not="!" fi if running_ipv6; then src6not="!" fi 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 ;; 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 '${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 local -a action_param=() local action_is_chain=0 case "${action}" in accept|ACCEPT) action="ACCEPT" if [ "${1}" = "with" ] then shift case "${1}" in limit|LIMIT) local -a action_param=("limit" "${2}" "${3}") shift 3 if [ "${1}" = "overflow" ] then action_param[3]="overflow" action_param[4]="${2}" shift 2 fi ;; recent|RECENT) local -a action_param=("recent" "${2}" "${3}" "${4}") shift 4 ;; knock|KNOCK) local -a 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 local -a action_param=("--reject-with" "${2}") shift 2 else local -a 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 local -a 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 local -a 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}" = "to-port" -o "${1}" = "to" ] then local -a 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 ;; tos|TOS) action="TOS" if [ "${1}" = "to" ] then local -a 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 local -a 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 ;; dscp|DSCP) action="DSCP" if [ "${1}" = "to" ] then if [ "${2}" = "class" ] then local -a action_param=("--set-dscp-class" "${2}") shift else local -a 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 local noowner=1 local nomirror=0 local nomac=0 shift ;; out) # this is outgoing traffic - ignore packet ownership if not in an interface if [ ! "${work_cmd}" = "interface" ] then local noowner=1 else local nomirror=1 fi local nomac=1 shift ;; nolog) local nolog=1 shift ;; noowner) local noowner=1 shift ;; softwarnings) local softwarnings=1 shift ;; nosoftwarnings) local softwarnings=0 shift ;; set_work_inface|SET_WORK_INFACE) swi=1 shift ;; set_work_outface|SET_WORK_OUTFACE) swo=1 shift ;; *) 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 srcnot= local dstnot= if running_both; then if [ "${src4not}" != "${src6not}" ] then error "Mixed use of 'not' with src4 and src6." && return 1 fi if [ "${dst4not}" != "${dst6not}" ] then error "Mixed use of 'not' with dst4 and dst6." && return 1 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" # ---------------------------------------------------------------------------------- # 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}" local 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 local negative_chain="${chain}.${DYNAMIC_CHAIN_COUNTER}" iptables_both ${table} -N "${negative_chain}" local negative_action="${action}" local 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 --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 --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= if running_ipv4; then for s in ${src4} do iptables ${table} -A "${negative_chain}" -s "${s}" -j RETURN done fi if running_ipv6; then for s in ${src6} do ip6tables ${table} -A "${negative_chain}" -s "${s}" -j RETURN done fi srcnot= src4=any src6=any fi if [ ! -z "${dstnot}" ] then local d= if running_ipv4; then for d in ${dst4} do iptables ${table} -A "${negative_chain}" -d "${d}" -j RETURN done fi if running_ipv6; then for d in ${dst6} do ip6tables ${table} -A "${negative_chain}" -d "${d}" -j RETURN done fi 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= for sp in ${sport} do local pr= 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= for dp in ${dport} do local pr= 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= for pr in ${proto} do local -a proto_arg=() case ${pr} in any|ANY) ;; *) local -a 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[@]}" fi if running_ipv6; then rule_action_param ip6tables "${negative_action}" "${pr}" "" "" "${table}" "${action_param[@]}" -- ${table} -A "${negative_chain}" "${proto_arg[@]}" fi local -a action_param=() done fi fi # ---------------------------------------------------------------------------------- # Process the positive rules # addrtype (srctype, dsttype) local -a addrtype_arg=() local -a stp_arg=() local -a dtp_arg=() if [ ! -z "${srctype}${dsttype}" ] then local -a addrtype_arg=("-m" "addrtype") if [ ! -z "${srctype}" ] then local -a stp_arg=(${srctypenot} "--src-type" "${srctype}") fi if [ ! -z "${dsttype}" ] then local -a dtp_arg=(${dsttypenot} "--dst-type" "${dsttype}") fi fi # state local -a state_arg=() if [ ! -z "${state}" ] then # local -a state_arg=("-m" "state" ${statenot} "--state" "${state}") local -a state_arg=("-m" "conntrack" ${statenot} "--ctstate" "${state}") fi # limit local -a limit_arg=() if [ ! -z "${limit}" ] then local -a limit_arg=("-m" "limit" "--limit" "${limit}" "--limit-burst" "${burst}") fi # iplimit local -a iplimit_arg=() if [ ! -z "${iplimit}" ] then local -a iplimit_arg=("-m" "iplimit" "--iplimit-above" "${iplimit}" "--iplimit-mask" "${iplimit_mask}") fi # log mode selection local -a logopts_arg=() if [ "${FIREHOL_LOG_MODE}" = "ULOG" ] then local -a logopts_arg=("--ulog-prefix=${FIREHOL_LOG_PREFIX}${logtxt}:") elif [ "${FIREHOL_LOG_MODE}" = "NFLOG" ] then local -a logopts_arg=("--nflog-prefix=${FIREHOL_LOG_PREFIX}${logtxt}:") else local -a logopts_arg=("--log-level" "${loglevel}" "--log-prefix=${FIREHOL_LOG_PREFIX}${logtxt}:") fi # log / loglimit local logrule= case "${log}" in '') logrule=none ;; limit) logrule=limit ;; normal) logrule=normal ;; *) error "Unknown log value '${log}'." ;; esac # uid local tuid= for tuid in ${uid} do local -a uid_arg=() local -a owner_arg=() case ${tuid} in any|ANY) ;; *) local -a owner_arg=("-m" "owner") local -a uid_arg=("--uid-owner" "${tuid}") ;; esac # gid local tgid= for tgid in ${gid} do local -a gid_arg=() case ${tgid} in any|ANY) ;; *) local -a owner_arg=("-m" "owner") local -a gid_arg=("--gid-owner" "${tgid}") ;; esac # pid local tpid= for tpid in ${pid} do local -a pid_arg=() case ${tpid} in any|ANY) ;; *) local -a owner_arg=("-m" "owner") local -a pid_arg=("--pid-owner" "${tpid}") ;; esac # sid local tsid= for tsid in ${sid} do local -a sid_arg=() case ${tsid} in any|ANY) ;; *) local -a owner_arg=("-m" "owner") local -a sid_arg=("--sid-owner" "${tsid}") ;; esac # cmd local tcmd= for tcmd in ${cmd} do local -a cmd_arg=() case ${tcmd} in any|ANY) ;; *) local -a owner_arg=("-m" "owner") local -a cmd_arg=("--cmd-owner" "${tcmd}") ;; esac # mark local tmark= for tmark in ${mark} do local -a mark_arg=() case ${tmark} in any|ANY) ;; *) local -a mark_arg=("-m" "mark" "--mark" "${tmark}") ;; esac # tos local ttos= for ttos in ${tos} do local -a tos_arg=() case ${ttos} in any|ANY) ;; *) local -a tos_arg=("-m" "tos" "--tos" "${ttos}") ;; esac # dscp local tdscp= for tdscp in ${dscp} do local -a dscp_arg=() case ${tdscp} in any|ANY) ;; *) local -a dscp_arg=("-m" "dscp" "--dscp${dscptype}" "${tdscp}") ;; esac # proto local pr= for pr in ${proto} do local -a proto_arg=() case ${pr} in any|ANY) ;; *) local -a proto_arg=("-p" "${pr}") ;; esac # inface local inf= for inf in ${inface} do local -a inf_arg=() case ${inf} in any|ANY) ;; *) local -a inf_arg=("-i" "${inf}") ;; esac # outface local outf= for outf in ${outface} do local -a outf_arg=() case ${outf} in any|ANY) ;; *) local -a outf_arg=("-o" "${outf}") ;; esac # physin local inph= for inph in ${physin} do local -a inph_arg=() case ${inph} in any|ANY) ;; *) local -a physdev_arg=("-m" "physdev") local -a inph_arg=("--physdev-in" "${inph}") ;; esac # physout local outph= for outph in ${physout} do local -a outph_arg=() case ${outph} in any|ANY) ;; *) local -a physdev_arg=("-m" "physdev") local -a outph_arg=("--physdev-out" "${outph}") ;; esac # sport local sp= for sp in ${sport} do local -a sp_arg=() case ${sp} in any|ANY) ;; *) local -a sp_arg=("--sport" "${sp}") ;; esac # dport local dp= for dp in ${dport} do local -a dp_arg=() case ${dp} in any|ANY) ;; *) local -a dp_arg=("--dport" "${dp}") ;; esac # mac local mc= for mc in ${mac} do local -a mc_arg=() case ${mc} in any|ANY) ;; *) local -a mc_arg=("-m" "mac" "--mac-source" "${mc}") ;; esac if running_ipv4; then # src4 local s= for s in ${src4} do local -a s_arg=() case ${s} in any|ANY) ;; *) local -a s_arg=("-s" "${s}") ;; esac # dst4 local d= for d in ${dst4} do local -a d_arg=() case ${d} in any|ANY) ;; *) local -a d_arg=("-d" "${d}") ;; esac # build the command declare -a 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 # do it! rule_action_param iptables "${action}" "${pr}" "${statenot}" "${state}" "${table}" "${action_param[@]}" -- ${table} -A "${chain}" "${basecmd[@]}" ${custom} done # dst4 done # src4 fi if running_ipv6; then # src6 local s= for s in ${src6} do local -a s_arg=() case ${s} in any|ANY) ;; *) local -a s_arg=("-s" "${s}") ;; esac # dst6 local d= for d in ${dst6} do local -a d_arg=() case ${d} in any|ANY) ;; *) local -a d_arg=("-d" "${d}") ;; esac # build the command declare -a 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 ip6tables ${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 ip6tables ${table} -A "${chain}" "${basecmd[@]}" ${custom} -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}" fi # do it! rule_action_param ip6tables "${action}" "${pr}" "${statenot}" "${state}" "${table}" "${action_param[@]}" -- ${table} -A "${chain}" "${basecmd[@]}" ${custom} done # dst6 done # src6 fi done # mac done # dport done # sport 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 } softwarning() { echo >&2 echo >&2 "--------------------------------------------------------------------------------" echo >&2 "WARNING" echo >&2 "WHAT : ${work_function}" echo >&2 "WHY :" "$@" printf >&2 "COMMAND: "; printf >&2 "%q " "${work_realcmd[@]}"; echo >&2 echo >&2 "MODE :" "${FIREHOL_NS_CURR}" echo >&2 "SOURCE : line ${FIREHOL_LINEID} of ${FIREHOL_CONFIG}" 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}' at ${FIREHOL_CONFIG} line ${FIREHOL_LINEID}" work_error=$[work_error + 1] echo >&2 echo >&2 "--------------------------------------------------------------------------------" echo >&2 "ERROR #: ${work_error}" echo >&2 "WHAT : ${work_function}" echo >&2 "WHY :" "$@" printf >&2 "COMMAND: "; printf >&2 "%q " "${work_realcmd[@]}"; echo >&2 echo >&2 "MODE :" "${FIREHOL_NS_CURR}" echo >&2 "SOURCE : line ${FIREHOL_LINEID} of ${FIREHOL_CONFIG}" 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" local id= case "${1}" in error) local type="ERROR " work_runtime_error=$[work_runtime_error + 1] local id="# ${work_runtime_error}." ;; warn) local type="WARNING" local id="This might or might not affect the operation of your firewall." ;; *) work_runtime_error=$[work_runtime_error + 1] local 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}"; shift local line="${1}"; shift syslog err "Runtime ${type} '${id}'. Source ${FIREHOL_CONFIG} line ${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 ${line} of ${FIREHOL_CONFIG}" 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. chain_exists() { local chain="${1}" if running_ipv4; then test -f "${FIREHOL_CHAINS_DIR}/${chain}.4" && return 1 fi if running_ipv6; then 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}" local newchain="${2}" local 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 ${TOUCH_CMD} "${FIREHOL_CHAINS_DIR}/${newchain}.4" fi if running_ipv6; then ${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}" # The current subcommand: server/client/route local services="${2}" # The services to implement shift 2 local service= for service in $services do local servname="${service}" test "${service}" = "custom" && local 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 local 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 local 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}" "$@" local 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 local fn="rules_${service}" set_work_function "Running complex rules function ${fn}() for ${type} '${service}'" "${fn}" "${mychain}" "${type}" "$@" local 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}"; shift local type="${1}"; shift local server="${1}"; shift local server_varname="server_${server}_ports" eval local server_ports="\$${server_varname}" local client_varname="client_${server}_ports" eval local 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 local varname="helper_${server}" eval local helpers="\$${varname}" local x= local varname="require_${server}_modules" eval local value="\$${varname}" for x in ${value} do require_kernel_module $x || return 1 done if [ ${FIREHOL_NAT} -eq 1 ] then local varname="require_${server}_nat_modules" eval local 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 "# CONF:%3s>>> " ${FIREHOL_LINEID} case $1 in 2) printf " " ;; *) ;; esac printf "%q " "${work_realcmd[@]}" printf "\n\n" ) >>${FIREHOL_OUTPUT} } work_realcmd_primary() { work_realcmd=("$@") test ${FIREHOL_CONF_SHOW} -eq 1 && show_work_realcmd 1 } work_realcmd_secondary() { work_realcmd=("$@") test ${FIREHOL_CONF_SHOW} -eq 1 && show_work_realcmd 2 } work_realcmd_helper() { work_realcmd=("$@") test ${FIREHOL_CONF_SHOW} -eq 1 && show_work_realcmd 3 } wait_for_interface() { local iface=$1; shift local timeout=60 if [ -n "$1" ]; then timeout=$1 fi local start=`date +%s` local found=0 while [ "`date +%s`" -lt $(($start+$timeout)) -a $found -eq 0 ] do local addr=`ip addr show $iface 2> /dev/null | awk '$1 ~ /^inet$/ {print $2}'` if [ -n "$addr" ] then found=1 fi if [ $found -eq 0 ] then sleep 0.5 fi done if [ $found -eq 1 ] then # the interface is up return 0 else return 1 fi } # ------------------------------------------------------------------------------ # 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 if [ -z "${IPTABLES_CMD}" -o ! -x "${IPTABLES_CMD}" ]; then echo >&2 "Cannot find an executable iptables command." exit 0 fi if [ -z "${IP6TABLES_CMD}" -o ! -x "${IP6TABLES_CMD}" ]; then echo >&2 "Cannot find an executable ip6tables command." exit 0 fi 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 # ------------------------------------------------------------------------------ me="${0}" arg="${1}" shift case "${arg}" in explain) test ! -z "${1}" && softwarning "Arguments after parameter '${arg}' are ignored." firewall_policy_applied=1 FIREHOL_MODE="EXPLAIN" ;; helpme|wizard) test ! -z "${1}" && softwarning "Arguments after parameter '${arg}' are ignored." FIREHOL_MODE="WIZARD" ;; try) test ! -z "${1}" && test ${1} != "--" && softwarning "Arguments after parameter '${arg}' are ignored." FIREHOL_MODE="START" FIREHOL_TRY=1 ;; start) test ! -z "${1}" && test ${1} != "--" && softwarning "Arguments after parameter '${arg}' are ignored." FIREHOL_MODE="START" FIREHOL_TRY=0 ;; stop) FIREHOL_MODE="STOP" test ! -z "${1}" && softwarning "Arguments after parameter '${arg}' are ignored." test -f "${FIREHOL_LOCK_DIR}/firehol" && ${RM_CMD} -f "${FIREHOL_LOCK_DIR}/firehol" test -f "${FIREHOL_LOCK_DIR}/iptables" && ${RM_CMD} -f "${FIREHOL_LOCK_DIR}/iptables" echo -n $"FireHOL: Clearing Firewall:" load_kernel_module ip_tables load_kernel_module ip6_tables tables=`${CAT_CMD} /proc/net/ip_tables_names` tables6=`${CAT_CMD} /proc/net/ip6_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 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 success $"FireHOL: Clearing Firewall:" echo exit 0 ;; restart|force-reload) test ! -z "${1}" && test ${1} != "--" && softwarning "Arguments after parameter '${arg}' are ignored." FIREHOL_MODE="START" ;; condrestart) test ! -z "${1}" && test ${1} != "--" && softwarning "Arguments after parameter '${arg}' are ignored." test -f "${FIREHOL_LOCK_DIR}/firehol" || exit 0 FIREHOL_MODE="START" ;; status) test ! -z "${1}" && softwarning "Arguments after parameter '${arg}' are ignored." ( echo echo echo "--- MANGLE IPv4 ----------------------------------------------------------------" echo ${IPTABLES_CMD} -t mangle -nxvL echo echo echo "--- MANGLE IPv6 ----------------------------------------------------------------" echo ${IP6TABLES_CMD} -t mangle -nxvL echo echo echo "--- NAT IPv4 -------------------------------------------------------------------" echo ${IPTABLES_CMD} -t nat -nxvL 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 echo echo echo "--- FILTER IPv4 ----------------------------------------------------------------" echo ${IPTABLES_CMD} -nxvL echo echo echo "--- FILTER IPv6 ----------------------------------------------------------------" echo ${IP6TABLES_CMD} -nxvL ) | 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:" load_kernel_module ip_tables load_kernel_module ip6_tables tables=`${CAT_CMD} /proc/net/ip_tables_names` tables6=`${CAT_CMD} /proc/net/ip6_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 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 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" ;; *) if [ ! -z "${arg}" -a -f "${arg}" ] then FIREHOL_MODE="START" FIREHOL_TRY=1 FIREHOL_CONFIG="${arg}" arg="${1}" test "${arg}" = "--" && arg="" && shift test -z "${arg}" && arg="try" case "${arg}" in start) FIREHOL_TRY=0 ;; try) FIREHOL_TRY=1 ;; debug) FIREHOL_MODE="DEBUG" FIREHOL_TRY=0 ;; *) echo "Cannot accept command line argument '${arg}' 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} "${me}" |\ ${GREP_CMD} -e "^server_.*_ports=" |\ ${CUT_CMD} -d '=' -f 1 |\ ${SED_CMD} "s/^server_//" |\ ${SED_CMD} "s/_ports\$//" # The complex services ${CAT_CMD} "${me}" |\ ${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 the next arg if it is -- test "${1}" = "--" && shift 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_CONFIG="Interactive User Input" FIREHOL_LINEID="1" 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}" FIREHOL_LINEID=$[FIREHOL_LINEID + 1] 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}"; shift local def="${1}"; shift echo >&2 while [ 1 = 1 ] do printf >&2 "%s [%s] > " "${prompt}" "${def}" read local ans="${REPLY}" test -z "${ans}" && ans="${def}" local c=0 while [ $c -le $# ] do eval local 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}"; shift local net="${1}"; shift 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} local i2=${2} local i3=${3} local i4=${4} set -- `echo ${net} | ${TR_CMD} './' ' '` local n1=${1} local n2=${2} local n3=${3} local n4=${4} local 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 local 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}"; shift local net="${1}"; shift 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} local i2=${2} local i3=${3} local i4=${4} local i5=${5:-32} set -- `echo ${net} | ${TR_CMD} './' ' '` local n1=${1} local n2=${2} local n3=${3} local n4=${4} local 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} local i2=${2} local i3=${3} local i4=${4} local 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 "${FIREHOL_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] echo echo "# Router No ${x}." echo "# Clients on ${inface} (from ${src}) accessing servers on ${outface} (to ${dst})." echo "# TODO: Change \"router${x}\" to something with meaning to you." echo "# TODO: Check the optional rule parameters (src/dst)." echo "router router${x} 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 ----------------------------------------------------------- fixed_save() { local command="$1" local tmp="${FIREHOL_DIR}/iptables-save-$$" local 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} "s/--uid-owner !/! --uid-owner /g" |\ ${SED_CMD} "s/--gid-owner !/! --gid-owner /g" |\ ${SED_CMD} "s/--pid-owner !/! --pid-owner /g" |\ ${SED_CMD} "s/--sid-owner !/! --sid-owner /g" |\ ${SED_CMD} "s/--cmd-owner !/! --cmd-owner /g" err=$? ${RM_CMD} -f $tmp >/dev/null 2>&1 return $err } echo -n $"FireHOL: Saving your old firewall to a temporary file:" fixed_save ${IPTABLES_SAVE_CMD} >${FIREHOL_SAVED} status4=$? fixed_save ${IP6TABLES_SAVE_CMD} >${FIREHOL_SAVED6} status6=$? if [ $status4 -eq 0 -a $status6 -eq 0 ] then success $"FireHOL: Saving your old firewall to a temporary file:" echo else ${RM_CMD} -f "${FIREHOL_SAVED}" "${FIREHOL_SAVED6}" failure $"FireHOL: Saving your old firewall to a temporary file:" echo exit 1 fi # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # clear all chains firehol_filter_chains= firehol_filter6_chains= initialize_firewall() { load_kernel_module ip_tables load_kernel_module ip6_tables load_kernel_module nf_conntrack for m in ${FIREHOL_KERNEL_MODULES} do postprocess -ne load_kernel_module $m done if [ $FIREHOL_ROUTING -eq 1 ] then postprocess ${SYSCTL_CMD} -w "net.ipv4.ip_forward=1" fi # Find all tables supported local t= local tables=`${CAT_CMD} /proc/net/ip_tables_names` local tables6=`${CAT_CMD} /proc/net/ip6_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. local 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. local c= for c in ${chains} do local policy=ACCEPT if [ "${t}" = "filter" ] then eval "policy=\${FIREHOL_${c}_ACTIVATION_POLICY}" fi ${IPTABLES_CMD} -t "${t}" -P "${c}" $policy || exit 1 done done 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. local 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. local c= for c in ${chains} do local 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: # 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 ${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 } # drop everything finalize_firewall() { # Make it drop everything on table 'filter'. local c= for c in ${firehol_filter_chains} do ${IPTABLES_CMD} -t filter -P "${c}" DROP || exit 1 done for c in ${firehol_filter6_chains} do ${IP6TABLES_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 1 ${IPTABLES_CMD} -D OUTPUT 1 ${IPTABLES_CMD} -D FORWARD 1 ${IP6TABLES_CMD} -D INPUT 1 ${IP6TABLES_CMD} -D OUTPUT 1 ${IP6TABLES_CMD} -D FORWARD 1 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 # ------------------------------------------------------------------------------ # Create a small awk script that inserts line numbers in the configuration file # just before each known directive. # These line numbers will be used for debugging the configuration script. ${CAT_CMD} >"${FIREHOL_TMP}.awk" <<"EOF" /^[[:space:]]*action[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*blacklist[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*classify[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*client[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*connmark[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*dnat[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*dscp[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*ecn_shame[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*group[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*interface[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*iptables[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*ip6tables[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*mac[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*mark[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*masquerade[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*nat[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*policy[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*postprocess[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*protection[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*redirect[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*require_kernel_module[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*router[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*route[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*server[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*snat[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*tcpmss[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*tos[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*transparent_squid[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*transparent_proxy[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } /^[[:space:]]*version[[:space:]]/ { printf "FIREHOL_LINEID=${LINENO} " } { print } EOF # at the same time, replace all ${IPTABLES_CMD} references with just # the word 'iptables' to protect the currently running firewall ${CAT_CMD} ${FIREHOL_CONFIG} | ${SED_CMD} "s|${IPTABLES_CMD}|iptables|g" | ${SED_CMD} "s|${IP6TABLES_CMD}|ip6tables|g" | gawk_cmd -f "${FIREHOL_TMP}.awk" >${FIREHOL_TMP} ${RM_CMD} -f "${FIREHOL_TMP}.awk" # ------------------------------------------------------------------------------ # 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. { source ${FIREHOL_TMP} "$@"; } # Run the configuration as a normal script. source_status=$? [ $source_status -ne 0 ] && ret=$[ret + 1] [ $source_status -ne 0 ] && FIREHOL_CLEAN_TMP=0 FIREHOL_LINEID="FIN" 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 # 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 if [ "${FIREHOL_MODE}" = "DEBUG" ] then ${CAT_CMD} ${FIREHOL_OUTPUT}.fast exit 1 fi 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. source ${FIREHOL_OUTPUT} "${@}" if [ $? -ne 0 ] then work_runtime_error=$[work_runtime_error+1] else # attempt to restore this firewall from the generated commands ${IPTABLES_RESTORE_CMD} <${FIREHOL_OUTPUT}.fast >${FIREHOL_OUTPUT}.log 2>&1 if [ $? -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] # the rest of the script will restore the original firewall else finalize_firewall fi fi else if [ "${FIREHOL_MODE}" = "DEBUG" ] then ${CAT_CMD} ${FIREHOL_OUTPUT} exit 1 fi 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 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. 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}" FIREHOL_ACTIVATED_SUCCESSFULLY=1 # Startup service locking. if [ -d "${FIREHOL_LOCK_DIR}" ] then ${TOUCH_CMD} "${FIREHOL_LOCK_DIR}/iptables" ${TOUCH_CMD} "${FIREHOL_LOCK_DIR}/firehol" fi # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX if [ ${FIREHOL_SAVE} -eq 1 ] then if [ -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 [ -z "${FIREHOL_AUTOSAVE6}" ] then error "Cannot find where to save ip6tables file. Please set FIREHOL_AUTOSAVE6." echo exit 1 fi echo -n $"FireHOL: Saving firewall to ${FIREHOL_AUTOSAVE}:" fixed_save ${IPTABLES_SAVE_CMD} >${FIREHOL_SAVED} 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 echo -n $"FireHOL: Saving IPv6 firewall to ${FIREHOL_AUTOSAVE6}:" fixed_save ${IP6TABLES_SAVE_CMD} >${FIREHOL_SAVED6} 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 # Save the list of modules we need to run to restore the firewall. if [ -f "${FIREHOL_SPOOL_DIR}/last_save_modules.sh" ] then mv "${FIREHOL_SPOOL_DIR}/last_save_modules.sh" "${FIREHOL_SPOOL_DIR}/last_save_modules.sh.old" fi mv "${FIREHOL_DIR}/modules_to_load.sh" "${FIREHOL_SPOOL_DIR}/last_save_modules.sh" if [ $? -gt 0 ] then error "Cannot save modules restoration script to '${FIREHOL_SPOOL_DIR}/last_save_modules.sh'." else chown root:root "${FIREHOL_SPOOL_DIR}/last_save_modules.sh" chmod 700 "${FIREHOL_SPOOL_DIR}/last_save_modules.sh" fi exit 0 fi