#!/bin/bash # # Bindle Binaries Linux Firewall # Copyright (c) 2019 David M. Syzdek . # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of David M. Syzdek nor the # names of its contributors may be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAVID M. SYZDEK BE LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # # Possible locations for firewall script: # # /etc/rc.d/rc.firewall - slackware init script # /usr/sbin/bbfw - generic firewall script location # # Files are processed in the following order: # # /etc/firewall.d/global.d/pre-hook - centrally maintained pre-hook script # /etc/firewall.d/apps.d/*/pre-hook - app pre-hook script # /etc/firewall.d/cluster.d/pre-hook - cluster specific pre-hook script # /etc/firewall.d/pre-hook - host specific pre-hook script # # /etc/firewall.d/global.d/ipsets - centrally maintained IP sets # /etc/firewall.d/apps.d/*.ipsets - app specific IP sets # /etc/firewall.d/cluster.d/ipsets - cluster specific IP sets # /etc/firewall.d/ipsets - host specific IP sets # # /etc/firewall.d/apps.d/*.rules4 - app specific IPv4 firewall rules # /etc/firewall.d/apps.d/*.rules6 - app specific IPv6 firewall rules # /etc/firewall.d/apps.d/*.rules - app specific IPv4/IPv6 firewall rules # # /etc/firewall.d/rules4 - host specific IPv4 firewall rules # /etc/firewall.d/rules6 - host specific IPv6 firewall rules # /etc/firewall.d/rules - host specific IPv4/IPv6 firewall rules # # /etc/firewall.d/cluster.d/rules4 - cluster specific IPv4 firewall rules # /etc/firewall.d/cluster.d/rules6 - cluster specific IPv6 firewall rules # /etc/firewall.d/cluster.d/rules - cluster specific IPv4/IPv6 firewall rules # # /etc/firewall.d/global.d/rules4 - centrally maintained IPv4 rules # /etc/firewall.d/global.d/rules6 - centrally maintained IPv6 rules # /etc/firewall.d/global.d/rules - centrally maintained IPv4/IPv6 rules # # /etc/firewall.d/global.d/post-hook - centrally maintained post-hook script # /etc/firewall.d/apps.d/*/post-hook - app post-hook script # /etc/firewall.d/cluster.d/post-hook - host specific post-hook script # /etc/firewall.d/post-hook - host specific post-hook script # ############### # ENVIRONMENT # ############### PATH=/usr/bin:/bin:/usr/sbin:/sbin export PATH # determine prefix of conf files FW_PREFIX=@sysconfdir@/firewall.d/ FW_CONF=@sysconfdir@/bbfw.conf FW_QUIET=no FW_VERBOSE=no FW_ORDER_PRE="global apps cluster local" FW_ORDER_POST="global apps cluster local" FW_ORDER_IPSETS="global apps cluster local" FW_ORDER_IPTABLES="apps local cluster global" ########### # LOGGING # ########### # sets script/package information PROGRAM_NAME="$(basename "${0}")" PACKAGE_NAME="$(echo '@PACKAGE_NAME@' |sed -e 's/[@].*[@]//g')" PACKAGE_VERSION="$(echo '@PACKAGE_VERSION@' |sed -e 's/[@].*[@]//g')" PACKAGE_BUGREPORT="$(echo '@PACKAGE_BUGREPORT@' |sed -e 's/[@].*[@]//g')" # If possible, log events in /var/log/messages: if [ -f /var/run/syslogd.pid -a -x logger ]; then LOGGER="logger -s -t ${PROGRAM_NAME}[$$]" else # output to stdout/stderr: LOGGER="sed -e s/^/${PROGRAM_NAME}:/g" fi ############# # FUNCTIONS # ############# clean_file() { DIRTY_FILE="${1}" cut -d# -f1 "${DIRTY_FILE}" \ |sed -n 'H; ${ x; s/\\\n//g; s/\n //g; p}' \ |sed -e 's/[[:space:]]\{1,\}/ /g' } config_option() { if test ! -f "${FW_CONF}";then echo "${2}" |tr "${3:-0}" "${4:-0}" return 0 fi TMP_STR="$( egrep \ -i \ "^[[:space:]]{0,}${1}[[:space:]]{0,}=" \ "${FW_CONF}" \ |sed \ -e "s/^[[:space:]]\{0,\}${1}[[:space:]]\{0,\}=[[:space:]]\{0,\}//g" \ -e "s/[[:space:]]\{0,\}$//g" \ -e "s/^[[:space:]]\{0,\}$//g" )" if test -z "${TMP_STR}";then echo "${2}" else echo "${TMP_STR}" fi |tr "${3:-0}" "${4:-0}" } dump() { ipset list |sed -e "s/^/ipset: /g" echo "" for TABLE in $(iptables-save |grep '^*' |sed -e 's/^*//g' |sort);do PREFIX="$(printf "%-9s" "${TABLE}:")" iptables -t ${TABLE} -L |sed -e "s/^/iptables ${PREFIX} /g" echo "" done for TABLE in $(ip6tables-save |grep '^*' |sed -e 's/^*//g' |sort);do PREFIX="$(printf "%-9s" "${TABLE}:")" ip6tables -t ${TABLE} -L |sed -e "s/^/ip6tables ${PREFIX} /g" echo "" done } name_appfile() { basename "$(dirname "${1}")" } find_appfile() { find -L \ ${FW_PREFIX}/apps.d/ \ -regextype posix-extended \ -regex "^${FW_PREFIX}/apps.d/[-.a-zA-Z0-9]+[./]${1}$" \ -type f \ 2> /dev/null } run_hook() { TYPE="${1}" FILE="${2}" DESC="${3}" if test ! -x "${FILE}";then return 0; fi if test ! -z "${DESC}" && test -z "${FW_QUIET}";then echo "${DESC}" fi ${FILE} ${TYPE} } load() { TYPE="${1}" FILE="${2}" DESC="${3}" if test ! -f "${FILE}";then return 0; fi if test ! -z "${DESC}" && test -z "${FW_QUIET}";then echo "${DESC}" fi case "${TYPE}" in 'ipset') clean_file ${FILE} |ipset -! restore;; 'iptables') clean_file ${FILE} |iptables-restore -n ${FW_VERBOSE};; 'ip6tables') clean_file ${FILE} |ip6tables-restore -n ${FW_VERBOSE};; *) ;; esac } # Function to start the network: start() { # run pre-hooks for LEVEL in ${FW_ORDER_PRE};do case "${LEVEL}" in global) run_hook start ${FW_PREFIX}/global.d/pre-hook "running global pre-hook start ..." ;; apps) for FILE in $( find_appfile pre-hook );do run_hook start ${FILE} "running app pre-hook start (App: $(name_appfile ${FILE})) ..." done ;; cluster) run_hook start ${FW_PREFIX}/cluster.d/pre-hook "running cluster pre-hook start ..." ;; local) run_hook start ${FW_PREFIX}/pre-hook "running local pre-hook start ..." ;; *) echo "Unknown pre-hook order \"${LEVEL}\"" ;; esac done # create IP sets for LEVEL in ${FW_ORDER_IPSETS};do case "${LEVEL}" in global) load ipset ${FW_PREFIX}/global.d/ipsets "adding global IP sets" ;; apps) for FILE in $( find_appfile ipsets );do load ipset ${FILE} "adding IP sets (App: $(name_appfile ${FILE})) ..." done ;; cluster) load ipset ${FW_PREFIX}/cluster.d/ipsets "adding cluster IP sets" ;; local) load ipset ${FW_PREFIX}/ipsets "adding local IP sets" ;; *) echo "Unknown ipsets order \"${LEVEL}\"" ;; esac done # loads iptables/ip6tables for LEVEL in ${FW_ORDER_IPTABLES};do case "${LEVEL}" in apps) for FILE in $( find_appfile rules4 );do load iptables ${FILE} "adding IPv4 rules (App: $(name_appfile ${FILE})) ..." done for FILE in $( find_appfile rules6 );do load ip6tables ${FILE} "adding IPv6 rules (App: $(name_appfile ${FILE})) ..." done for FILE in $( find_appfile rules );do load iptables ${FILE} "adding IPv4/IPv6 rules (App: $(name_appfile ${FILE})) ..." load ip6tables ${FILE} done ;; local) load iptables ${FW_PREFIX}/rules4 "adding IPv4 local rules" load ip6tables ${FW_PREFIX}/rules6 "adding IPv6 local rules" load iptables ${FW_PREFIX}/rules "adding IPv4/IPv6 local rules" load ip6tables ${FW_PREFIX}/rules ;; cluster) load iptables ${FW_PREFIX}/cluster.d/rules4 "adding IPv4 cluster rules" load ip6tables ${FW_PREFIX}/cluster.d/rules6 "adding IPv6 cluster rules" load iptables ${FW_PREFIX}/cluster.d/rules "adding IPv4/IPv6 cluster rules" load ip6tables ${FW_PREFIX}/cluster.d/rules ;; global) load iptables ${FW_PREFIX}/global.d/rules4 "adding IPv4 global rules" load ip6tables ${FW_PREFIX}/global.d/rules6 "adding IPv6 global rules" load iptables ${FW_PREFIX}/global.d/rules "adding IPv4/IPv6 global rules" load ip6tables ${FW_PREFIX}/global.d/rules ;; *) echo "Unknown iptables order \"${LEVEL}\"" ;; esac done # run post-hooks for LEVEL in ${FW_ORDER_POST};do case "${LEVEL}" in global) run_hook start ${FW_PREFIX}/global.d/post-hook "running global post-hook start ..." ;; apps) for FILE in $( find_appfile post-hook );do run_hook start ${FILE} "running app post-hook start (App: $(name_appfile ${FILE})) ..." done ;; cluster) run_hook start ${FW_PREFIX}/cluster.d/post-hook "running cluster post-hook start ..." ;; local) run_hook start ${FW_PREFIX}/post-hook "running local post-hook start ..." ;; *) echo "Unknown post-hook order \"${LEVEL}\"" ;; esac done } # Function to stop the network: stop() { # run pre-hooks for LEVEL in ${FW_ORDER_PRE};do case "${LEVEL}" in global) run_hook stop ${FW_PREFIX}/global.d/pre-hook "running global pre-hook stop ..." ;; apps) for FILE in $( find_appfile pre-hook );do run_hook stop ${FILE} "running app pre-hook stop (App: $(name_appfile ${FILE})) ..." done ;; cluster) run_hook stop ${FW_PREFIX}/cluster.d/pre-hook "running cluster pre-hook stop ..." ;; local) run_hook stop ${FW_PREFIX}/pre-hook "running local pre-hook stop ..." ;; *) echo "Unknown pre-hook order \"${LEVEL}\"" ;; esac done # flushes iptables for TABLE in $(iptables-save |grep '^*' |sed -e 's/^*//g' |sort);do if test -z "${FW_QUIET}";then echo "Flushing rules from ${TABLE} table (IPv4)" fi for CHAIN in $(iptables-save -t ${TABLE} |grep '^:' |cut -d: -f2 |grep -v ' - ' |cut -d\ -f1);do iptables -t ${TABLE} -P ${CHAIN} ACCEPT done iptables -t ${TABLE} -F for CHAIN in $(iptables-save -t ${TABLE} |grep '^:' |cut -d: -f2 |grep ' - ' |cut -d\ -f1);do iptables -t ${TABLE} -X ${CHAIN} done done 2> /dev/null # flushes ip6tables for TABLE in $(ip6tables-save |grep '^*' |sed -e 's/^*//g' |sort);do if test -z "${FW_QUIET}";then echo "Flushing rules from ${TABLE} table (IPv6)" fi for CHAIN in $(ip6tables-save -t ${TABLE} |grep '^:' |cut -d: -f2 |grep -v ' - ' |cut -d\ -f1);do ip6tables -t ${TABLE} -P ${CHAIN} ACCEPT done ip6tables -t ${TABLE} -F for CHAIN in $(ip6tables-save -t ${TABLE} |grep '^:' |cut -d: -f2 |grep ' - ' |cut -d\ -f1);do ip6tables -t ${TABLE} -X ${CHAIN} done done 2> /dev/null # flushes ipsets if test -z "${FW_QUIET}";then echo "Destroying all IP sets ..." fi for SET in $(ipset save |grep '^create' |grep ' list:' |awk '{print$2}');do ipset destroy "${SET}" done for SET in $(ipset save |grep '^create' |awk '{print$2}');do ipset destroy "${SET}" done # run post-hooks for LEVEL in ${FW_ORDER_POST};do case "${LEVEL}" in global) run_hook stop ${FW_PREFIX}/global.d/post-hook "running global post-hook stop ..." ;; apps) for FILE in $( find_appfile post-hook );do run_hook stop ${FILE} "running app post-hook stop (App: $(name_appfile ${FILE})) ..." done ;; cluster) run_hook stop ${FW_PREFIX}/cluster.d/post-hook "running cluster post-hook stop ..." ;; local) run_hook stop ${FW_PREFIX}/post-hook "running local post-hook stop ..." ;; *) echo "Unknown post-hook order \"${LEVEL}\"" ;; esac done } ############ ### MAIN ### ############ # sets options from configuration file FW_PREFIX="$( config_option firewall_dir "${FW_PREFIX}" )" FW_QUIET="$( config_option quiet "${FW_QUIET}" A-Z a-z )" FW_VERBOSE="$( config_option verbose "${FW_VERBOSE}" A-Z a-z )" FW_ORDER_PRE="$( config_option order_prehook "${FW_ORDER_PRE}" A-Z a-z )" FW_ORDER_POST="$( config_option order_posthook "${FW_ORDER_POST}" A-Z a-z )" FW_ORDER_IPTABLES="$( config_option order_iptables "${FW_ORDER_IPTABLES}" A-Z a-z )" FW_ORDER_IPSETS="$( config_option order_ipsets "${FW_ORDER_IPSETS}" A-Z a-z )" # checks and normalizes options if test "x${FW_QUIET}" == "xno";then unset FW_QUIET elif test "x${FW_QUIET}" != "xyes";then echo "${PROGRAM_NAME}: invalid value for option 'quiet'" 1>&2 exit 1 fi if test "x${FW_VERBOSE}" == "xno";then unset FW_VERBOSE elif test "x${FW_VERBOSE}" != "xyes";then echo "${PROGRAM_NAME}: invalid value for verbose 'quiet'" 1>&2 exit 1 else FW_VERBOSE=-v fi case "$1" in -h|--help) echo "Usage: ${PROGRAM_NAME} [ start | stop | restart | dump ]" echo " ${PROGRAM_NAME} [ -h | --help ]" echo " ${PROGRAM_NAME} [ -v | --version ]" echo "" ;; -v|--version) echo "${PACKAGE_NAME} ${PACKAGE_VERSION} (bbfw)" ;; 'dump') dump ;; 'start') # "start" brings up all configured interfaces: echo "${PACKAGE_NAME} ${PACKAGE_VERSION}" | $LOGGER start 2>&1 | $LOGGER ;; 'stop') # "stop" takes down all configured interfaces: echo "${PACKAGE_NAME} ${PACKAGE_VERSION}" | $LOGGER stop 2>&1 | $LOGGER ;; 'restart') # "restart" restarts the network: echo "${PACKAGE_NAME} ${PACKAGE_VERSION}" | $LOGGER stop 2>&1 | $LOGGER start 2>&1 | $LOGGER ;; *) # The default is to bring up all configured interfaces: echo "Usage: ${PROGRAM_NAME} [ start | stop | restart | dump ]" echo " ${PROGRAM_NAME} [ -h | --help ]" echo " ${PROGRAM_NAME} [ -v | --version ]" echo "" ;; esac # End of scripts