#!/bin/sh

###############################################################################
#
# 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, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
###############################################################################

# IPv4 or IPv6?
FW_PROTO="ipv4"
FW_LOG_TAG="firewall"
if [ "$(basename $0)" == "firewall-start6" ]; then
    FW_PROTO="ipv6"
    FW_LOG_TAG="firewall6"
fi
export FW_PROTO

# Logging
export FW_FACILITY=${FW_FACILITY:-"local6"}

fw_logger()
{
    log_level=$1
    shift 1
    /usr/bin/logger -s -p ${FW_FACILITY}.${log_level} -t ${FW_LOG_TAG} "$@"
}

# Default script to execute
APP_LUA="/usr/clearos/apps/firewall/deploy/firewall.lua"

# Multipath synchronization flag
if [ "$1" == "--multipath-sync" ]; then
    shift 1
    MULTIPATH_SYNC="true"
    APP_LUA="/usr/clearos/apps/firewall/deploy/multipath-sync.lua"
fi

# Build a list of configured SSHd ports
SSHD_CONF=/etc/ssh/sshd_config
if [ -f "${SSHD_CONF}" ]; then
    LISTEN_ADDR="^ListenAddress[[:space:]]*"

    ANY_PORTS=$(egrep '^Port' ${SSHD_CONF} |\
        sed -e 's/^Port[[:space:]]*//g')
    IP4_PORTS=$(egrep "${LISTEN_ADDR}[0-9]" ${SSHD_CONF} |\
        sed -e "s/${LISTEN_ADDR}//g" -e 's/:/ /g' | awk '{ print $2 }')
    IP6_PORTS=$(egrep "${LISTEN_ADDR}\[" ${SSHD_CONF} |\
        sed -e "s/${LISTEN_ADDR}\[//g" -e 's/\]:/ /g' | awk '{ print $2 }')
fi

SSHD_PORTS_IP4=$(echo -n ${ANY_PORTS} ${IP4_PORTS} | xargs -n 1 | sort -n | uniq)
SSHD_PORTS_IP6=$(echo -n ${ANY_PORTS} ${IP6_PORTS} | xargs -n 1 | sort -n | uniq)

[ -z "${SSHD_PORTS_IP4}" ] && SSHD_PORTS_IP4=22
[ -z "${SSHD_PORTS_IP6}" ] && SSHD_PORTS_IP6=22

# ACCEPT and DROP default chain names
export FW_DROP="DROP"
export FW_ACCEPT="ACCEPT"

# Log all dropped packets?
export FW_LOG_DROPS="no"

# Configuration
export FW_ADHOC=${FW_ADHOC:-"/etc/clearos/firewall.d/local"}
export FW_CONF=${FW_CONF:-"/etc/clearos/firewall.conf"}
export FW_CUSTOM=${FW_CUSTOM:-"/etc/clearos/firewall.d/custom"}
export FW_TYPES="/etc/clearos/firewall.d/types"
export FW_APP_RULES="$(find /etc/clearos/firewall.d -maxdepth 1 | egrep '/[0-9]+-*' | sort -t - -n)"
export BANDWIDTH_CONF="/etc/clearos/bandwidth.conf"
export QOS_CONF="/etc/clearos/qos.conf"
export MULTIWAN_CONF="/etc/clearos/multiwan.conf"
export NETWORK_CONF="/etc/clearos/network.conf"
export PROTOCOL_CONF="/etc/clearos/protocol_filter.conf"

# Default mode
export FW_DEFAULT_MODE="trustedstandalone"

# Binaries and paths and IPv4/6-specific settings
if [ "$FW_PROTO" == "ipv4" ]; then
    APP_FW="/sbin/app-firewall"
    IPTABLES="/sbin/iptables -w"
    IPBIN="/sbin/ip"
    FW_LOCKFILE="/var/run/firewall.pid"
    FW_PANIC="IPv4 Firewall Error - Restricted Access Only"
    FW_PANIC_STAMP="/var/state/firewall/panic-stamp"
    FW_PANIC_EVENTID="SYS_FW_PANIC"
    SSHD_PORTS=${SSHD_PORTS_IP4}
else
    APP_FW="/sbin/app-firewall6"
    IPTABLES="/sbin/ip6tables -w"
    IPBIN="/sbin/ip -6"
    FW_LOCKFILE="/var/run/firewall6.pid"
    FW_PANIC="IPv6 Firewall Error - Restricted Access Only"
    FW_PANIC_STAMP="/var/state/firewall/panic-stamp6"
    FW_PANIC_EVENTID="SYS_FW6_PANIC"
    SSHD_PORTS=${SSHD_PORTS_IP6}
fi

export IPTABLES
export IPBIN
export MODPROBE="/sbin/modprobe"
export RMMOD="/sbin/rmmod"
export SYSCTL="/sbin/sysctl"
export SYSWATCH_STATE="/var/lib/syswatch/state"
export TCBIN="/sbin/tc"

# Pretend mode?
PRETEND=0
for (( i=0; i<$#; i++ )); do
    if [ ${BASH_ARGV[$i]} == "-p" ]; then
        PRETEND=1
        break
    fi
done

# TODO: detect installer environment properly
# Bail if we are in installer environment
CHECK="$(/sbin/pidof loader)"
[ -n "$CHECK" ] && exit 0

# Pull in firewall types
source $FW_TYPES || exit 1

# Pull in bandwidth configuration
[ -e "$BANDWIDTH_CONF" ] && source $BANDWIDTH_CONF

# Pull in QoS configuration
[ -e "$QOS_CONF" ] && source $QOS_CONF

# Pull in network configuration
[ -e "$NETWORK_CONF" ] && source $NETWORK_CONF

# Pull in multiwan configuration
[ -e "$MULTIWAN_CONF" ] && source $MULTIWAN_CONF

# Pull in protocol filter configuration
[ -e "$PROTOCOL_CONF" ] && source $PROTOCOL_CONF

# Pull in network configuration, export gateway device
source /etc/sysconfig/network
export GATEWAYDEV

# Pull in syswatch interface list
source $FW_CONF
if [ -f "$SYSWATCH_STATE" ]; then
    source $SYSWATCH_STATE
else
    SYSWATCH_WANIF="$EXTIF"
fi

# Remap RULES6 to RULES for IPv6 mode
if [ "$FW_PROTO" == "ipv6" ]; then
    RULES=$RULES6
fi

# TODO: A quick hack to determine proxy user authentication mode
USERAUTH="$(grep 'http_access.*webconfig_lan.*password' /etc/squid/squid.conf 2>/dev/null)"
if [ -n "$USERAUTH" ]; then
    SQUID_USER_AUTHENTICATION="on"
else
    SQUID_USER_AUTHENTICATION="off"
fi

# Export firewall configuration
export MODE EXTIF EXTIF_BACKUP EXTIF_STANDBY LANIF HOTIF DMZIF WIFIF BANDWIDTH_QOS \
    BANDWIDTH_UPSTREAM BANDWIDTH_UPSTREAM_BURST BANDWIDTH_UPSTREAM_CBURST \
    BANDWIDTH_DOWNSTREAM BANDWIDTH_DOWNSTREAM_BURST BANDWIDTH_DOWNSTREAM_CBURST \
    QOS_ENABLE QOS_ENGINE QOS_UPSTREAM QOS_DOWNSTREAM \
    QOS_UPSTREAM_BWRES QOS_DOWNSTREAM_BWRES QOS_UPSTREAM_BWLIMIT QOS_DOWNSTREAM_BWLIMIT \
    QOS_PRIOMARK4 QOS_PRIOMARK6 QOS_PRIOMARK4_CUSTOM QOS_PRIOMARK6_CUSTOM \
    SQUID_TRANSPARENT SQUID_USER_AUTHENTICATION IPSEC_SERVER PPTP_SERVER \
    PPTP_PASSTHROUGH_FORCE ONE_TO_ONE_NAT_MODE LANNET MULTIPATH \
    MULTIPATH_WEIGHTS RULES MULTIPATH_SKIP_DOWN_WANIF FW_LOG_DROPS \
    SYSWATCH_WANIF EGRESS_FILTERING PROTOCOL_FILTERING

# Panic mode rules.  If the firewall fails, then we run these rules to ensure
# we have something sane configured.
RunPanicRules() {
    if [ $PRETEND -eq 1 ]; then return; fi

    fw_logger error "Running firewall panic mode..."

    # Touch panic file.  Signals applications that there is something wrong.
    touch "$FW_PANIC_STAMP"

    # Send/update firewall panic event
    /usr/bin/eventsctl --send \
        --type $FW_PANIC_EVENTID --basename app-firewall --level critical \
        --origin firewall-start --auto-resolve "$FW_PANIC"

    for TABLE in filter nat mangle; do
        $IPTABLES -t $TABLE -F  # Flush all previous rules.
        $IPTABLES -t $TABLE -X  # Delete user-defined chains.
    done

    $IPTABLES -P INPUT DROP
    $IPTABLES -P OUTPUT DROP
    $IPTABLES -P FORWARD DROP

    # Allow ping for diagnostics
    ICMP=icmp
    if [ "$FW_PROTO" == "ipv6" ]; then
        ICMP=ipv6-icmp
    fi

    $IPTABLES -A INPUT -p $ICMP -j $FW_ACCEPT
    $IPTABLES -A OUTPUT -p $ICMP -j $FW_ACCEPT

    # Open Webconfig
    $IPTABLES -I INPUT -p tcp --dport 81 -j $FW_ACCEPT
    $IPTABLES -I OUTPUT -p tcp --sport 81 -j $FW_ACCEPT

    # Open SSHd
    for PORT in ${SSHD_PORTS}; do
        $IPTABLES -I INPUT -p tcp --dport ${PORT} -j $FW_ACCEPT
        $IPTABLES -I OUTPUT -p tcp --sport ${PORT} -j $FW_ACCEPT
    done

    # Allow DNS requests
    $IPTABLES -A INPUT -p udp --sport domain -j $FW_ACCEPT
    $IPTABLES -A OUTPUT -p udp --dport domain -j $FW_ACCEPT
    $IPTABLES -A INPUT -p tcp --sport domain -j $FW_ACCEPT
    $IPTABLES -A OUTPUT -p tcp --dport domain -j $FW_ACCEPT

    # Allow DHCP to startup
    $IPTABLES -A INPUT -p udp --dport bootpc --sport bootps -j $FW_ACCEPT
    $IPTABLES -A INPUT -p tcp --dport bootpc --sport bootps -j $FW_ACCEPT
    $IPTABLES -A OUTPUT -p tcp --sport bootpc --dport bootps -j $FW_ACCEPT
    $IPTABLES -A OUTPUT -p udp --sport bootpc --dport bootps -j $FW_ACCEPT

    # Allow high ports
    $IPTABLES -A OUTPUT -p tcp --sport 1024:65535 -j $FW_ACCEPT
    $IPTABLES -A INPUT -p tcp --dport 1024:65535 \
        -m state --state ESTABLISHED,RELATED -j $FW_ACCEPT

    # Allow everything on the loopback
    $IPTABLES -A INPUT -i lo -j $FW_ACCEPT
    $IPTABLES -A OUTPUT -o lo -j $FW_ACCEPT
}

# Load essential IPTables modules
if [ "$FW_PROTO" == "ipv4" ]; then
    $MODPROBE ip_tables
    $MODPROBE iptable_filter
    $MODPROBE iptable_mangle
    $MODPROBE iptable_nat
else
    $MODPROBE ip6_tables
    $MODPROBE ip6table_filter
    $MODPROBE ip6table_mangle
    $MODPROBE ip6table_nat
fi

# Run Lua application
RC=0
if (set -o noclobber; echo "$$" > "$FW_LOCKFILE") 2> /dev/null; then
    trap 'rm -f "$FW_LOCKFILE"; exit $RC' INT TERM EXIT

    if [ x$MULTIPATH_SYNC != "xtrue" ]; then
        # Remove panic stamp file
        rm -f "$FW_PANIC_STAMP"
    fi

    if ($APP_FW -w -s $@ $APP_LUA); then

        # Exit here if we were invoked only to sync multipath routing
        if [ x$MULTIPATH_SYNC == "xtrue" ]; then
            rm -f "$FW_LOCKFILE"
            exit $RC
        fi

        # Resolve any out-standing firewall panic events
        /usr/bin/eventsctl --mark-resolved --type $FW_PANIC_EVENTID

        ID=`grep ^software_id /etc/product | awk '{ print $3 }'`
        fw_logger notice "Running post-firewall: $ID"

        # Run Custom/ad-hoc rules
        # XXX: This used to be run before anything else, but because we are now
        # queueing rules until iptc_commit(), it makes no sense to run an
        # external script because whatever it does will be cleared by
        # iptc_commit().  Using the Advanced firewall rule creator in webconfig
        # will allow custom rules to be run before others.  Ad-hoc rules should
        # use -I to insert rules ahead of others if need be.
        if [ -e $FW_CUSTOM ]; then
            fw_logger notice "Running $FW_CUSTOM"
            source $FW_CUSTOM || RC=1
        fi

        if [ -e $FW_ADHOC ]; then
            fw_logger notice "Running $FW_ADHOC"
            source $FW_ADHOC || RC=1
        fi

        for FW_APP in $FW_APP_RULES; do
            fw_logger notice "Running $FW_APP"
            source $FW_APP || RC=1
        done
    else
        RC=1
        if [ x$MULTIPATH_SYNC != "xtrue" ]; then
            RunPanicRules
        fi
    fi

    rm -f "$FW_LOCKFILE"
else
    fw_logger warning "Failed to acquire lock: $FW_LOCKFILE (held by $(cat $FW_LOCKFILE))"
    exit 1
fi 

exit $RC

# vi: syntax=sh expandtab shiftwidth=4 softtabstop=4 tabstop=4
