#!/bin/ksh93 ### BEGIN INIT INFO # Provides: iptables # Required-Start: networking # Required-Stop: $local_fs # Default-Start: S # Default-Stop: 0 6 # Short-Description: Initialize iptables # Description: Bring/down up the IP firewall. ### END INIT INFO # NO '-e' because a faulty rule should not cause skipping subsequent rules # # The file should be installed as /lib/systemd/scripts/iptables typeset -r FPROG="${.sh.file}" PROG="${FPROG##*/}" typeset -r RULES_FILE='/etc/iptables.rules' \ SYSTEMD_SCRIPT='/lib/systemd/scripts/iptables' \ SYSV_SCRIPT='/etc/init.d/iptables' \ SYSTEMD_SVC='/lib/systemd/system/iptables.service' \ FW='/sbin/iptables' function showUsage { typeset X='--man' [[ -z $1 ]] && X='-?' getopts -a ${PROG} "${ print ${USAGE}; }" OPT $X } function getIP { [[ -z $1 ]] && return typeset X=${ ip addr show $1 ; } Y Y=${X#* inet } [[ -z ${.sh.match} ]] && return X=${Y%%/*} [[ -n ${.sh.match} ]] && print $X } function getIpByName { [[ -z $1 ]] && retun 0 typeset -A RES # since buggy/braindamaged/POSIX-incompatible ubuntu returns IPv6 addresses # even in IPv4 only environments, we need to use the non-standard DB # 'ahostsv4' - fuck. typeset DB='ahostsv4' while [[ -n $1 ]]; do getent ${DB} $1 | while read X Y ; do [[ $Y == 'RAW' ]] || continue A=( ${X//./ } ) T=${ printf "_%03d.%03d.%03d.%03d" ${A[@]} ; } RES["$T"]="$X" done shift done set -s ${!RES[@]} Y= for X ; do Y+=",${RES[$X]}" done print "${Y:1}" } function checkGlobals { typeset X if [[ -n ${INTERNAL_IF} ]]; then [[ -d /proc/sys/net/ipv4/conf/${INTERNAL_IF} ]] && \ IF_INT=${INTERNAL_IF} || \ print -u2 "Internal NIC '${INTERNAL_IF}' does not exist - ignored." fi if [[ -n ${EXTERNAL_IF} ]]; then [[ -d /proc/sys/net/ipv4/conf/${EXTERNAL_IF} ]] && \ IF_EXT=${EXTERNAL_IF} || \ print -u2 "External NIC '${EXTERNAL_IF}' does not exist - ignored." fi if [[ -z ${IF_INT} && -z ${IF_EXT} ]]; then X=${ uname -n ; } [[ $X =~ [0-9]$ ]] && X+='_0' || X+='0' if [[ -d /proc/sys/net/ipv4/conf/$X ]]; then IF_EXT="$X" elif [[ -d /proc/sys/net/ipv4/conf/eth0 ]]; then IF_EXT='eth0' else for X in ~(N)/proc/sys/net/ipv4/conf/* ; do [[ $X =~ (all|default|lo)$ || ! -d $X ]] && continue IF_EXT="$X" print -u2 "WARNING: Using interface $X as external NIC" break done fi fi if [[ -z ${IF_INT} || -z ${IF_EXT} ]]; then IS_GW=0 && IS_ROUTER=0 fi IP_INT=${ getIP ${IF_INT} ; } IP_EXT=${ getIP ${IF_EXT} ; } } # Actually this should be part of /etc/sysctl.conf - however, especially within # zone we cannot be sure, that interfaces are not already there. # See also: http://www.frozentux.net/ipsysctl-tutorial/chunkyhtml/theconfvariables.html function tuneNet { typeset F X IPv4='/proc/sys/net/ipv4' # Reverse Path Filtering: If the source of the received packet is not # reachable through the interface it came in, drop it (prevent spoofing). # NOTES: Has no effect if /all/ is not set to 1 as well! # RedHat also allows '2' for "reachable through _any_ interface" for F in ${IPv4}/conf/*/rp_filter; do [[ $F =~ /lo/rp_filter$ ]] && X=0 || X=1 print $X 2>&1 > $F|grep -v 'Read-only file system' print ${LOG_MARTIAN} 2>&1 >${F%/*}/log_martians|\ grep -v 'Read-only file system' done # No tuning of net.ipv4.tcp_syncookies - should stay 0 since we want SACK # and window scaling. n/a within zones. [[ -e ${IPv4}/tcp_sack ]] && print 1 >${IPv4}/tcp_sack [[ -e ${IPv4}/tcp_window_scaling ]] && print 1 >${IPv4}/tcp_window_scaling # En/Disable packet forwarding print ${IS_GW} 2>&1 >${IPv4}/ip_forward|grep -v 'Read-only file system' if (( ! IS_ROUTER )); then # Drop pakets with Strict Source Route (SSR) or Loose Source Routing # (LSR) option set. Our pakets take the default route(s). # We usually have only a single route and if one tells us something # different it is probably a fake or man-in-the-middle attack (MITM). # In turn we do not send such pakets as well. for F in ${IPv4}/conf/*/accept_source_route; do X=${F%/*} print 0 2>&1 >$X/accept_source_route|grep -v 'Read-only file system' print 0 2>&1 >$X/accept_redirects|grep -v 'Read-only file system' print 0 2>&1 >$X/send_redirects|grep -v 'Read-only file system' done fi } function deleteTables { print -u2 -f 'Flushing iptable rules: %T\n' now ${FW} -t filter -F ${FW} -t filter -X ${FW} -t nat -F ${FW} -t nat -X ${FW} -t mangle -F ${FW} -t mangle -X print 0 2>&1 >/proc/sys/net/ipv4/ip_forward|grep -v 'Read-only file system' print -u2 'Done.' return 0 } function dropWin { # Drop win stuff -> avoid log flooding ${FW} -A $1 -p tcp --dport netbios-ns -j DROP ${FW} -A $1 -p udp --dport netbios-ns -j DROP ${FW} -A $1 -p tcp --dport netbios-dgm -j DROP ${FW} -A $1 -p udp --dport netbios-dgm -j DROP ${FW} -A $1 -p tcp --dport netbios-ssn -j DROP ${FW} -A $1 -p udp --dport netbios-ssn -j DROP } function createChains { # limit: # When 10 pakets/s matched, skip logging pakets for the next 200ms (1s/5). # Burst gets re-charged by 1 every 200ms if no paket matched the rule => # full burst is avalable again after 2s (burst/limit using the same unit). # # For zones one should _NOT_ use -j LOG, since than messages from the GZ are # randomly spread across all GZ's kernel loggers (rsyslog) and NGZ born # messages end up in the nirwana aka nowhere. However, logging to the zone's # userland daemon like ulogd2 seems to work for GZ as well as NGZs. # # We log to the ulogd2 stack having 'group=0' set, so we do need to add here # something like '--nflog-group=2'. ULOG does not support --log-level ...! typeset LOG='-m limit --limit=5/s --limit-burst=10 -j NFLOG --nflog-prefix ' if [[ -n ${IF_EXT} ]]; then ${FW} -N NeverExt ${FW} -A NeverExt ${LOG} 'NEVERe: ' #--log-level crit ${FW} -A NeverExt -j REJECT ${FW} -N LogDropExt dropWin LogDropExt ${FW} -A LogDropExt ${LOG} 'EXT: ' ${FW} -A LogDropExt -j REJECT ${FW} -N ExtIn ${FW} -A INPUT -i ${IF_EXT} -j ExtIn ${FW} -A ExtIn -m state --state ESTABLISHED,RELATED -j ACCEPT # drop all INVALID or UNTRACKED pakets ${FW} -A ExtIn -m state ! --state NEW -j LogDropExt ${FW} -N ExtOut ${FW} -A OUTPUT -o ${IF_EXT} -j ExtOut ${FW} -A ExtOut -m state --state ESTABLISHED,RELATED -j ACCEPT # drop all INVALID or UNTRACKED pakets ${FW} -A ExtOut -m state ! --state NEW -j LogDropExt fi if [[ -n ${IF_INT} ]]; then ${FW} -N LogDropInt dropWin LogDropInt ${FW} -A LogDropInt ${LOG} 'INT: ' ${FW} -A LogDropInt -j REJECT ${FW} -N NeverInt ${FW} -A NeverInt ${LOG} 'NEVERi: ' #--log-level crit ${FW} -A NeverInt -j REJECT ${FW} -N IntIn ${FW} -A INPUT -i ${IF_INT} -j IntIn ${FW} -A IntIn -m state --state ESTABLISHED,RELATED -j ACCEPT ${FW} -A IntIn -m state ! --state NEW -j LogDropInt ${FW} -N IntOut ${FW} -A OUTPUT -o ${IF_INT} -j IntOut ${FW} -A IntOut-m state --state ESTABLISHED,RELATED -j ACCEPT ${FW} -A IntOut-m state ! --state NEW -j LogDropInt fi if (( IS_GW )); then ${FW} -N NeverFw ${FW} -A NeverFw ${LOG} 'NEVERf: ' --log-level crit ${FW} -A NeverFw -j REJECT ${FW} -N LogDropIntExt dropWin LogDropIntExt ${FW} -A LogDropIntExt ${LOG} 'INTe: ' ${FW} -A LogDropIntExt -j REJECT ${FW} -N LogDropExtInt dropWin LogDropExtInt ${FW} -A LogDropExtInt ${LOG} 'EXTi: ' ${FW} -A LogDropExtInt -j REJECT ${FW} -N IntExt ${FW} -N ExtInt ${FW} -A FORWARD -i ${IF_INT} -o ${IF_EXT} -j IntExt ${FW} -A FORWARD -i ${IF_EXT} -o ${IF_INT} -j ExtInt ${FW} -A FORWARD -j NeverFw ${FW} -A ExtInt -m state --state ESTABLISHED,RELATED -j ACCEPT fi ${FW} -N Icmp } function setupIcmpChain { ${FW} -A Icmp -p icmp --icmp-type ping -j ACCEPT ${FW} -A Icmp -p icmp --icmp-type pong -j ACCEPT ${FW} -A Icmp -p icmp --icmp-type destination-unreachable -j ACCEPT ${FW} -A Icmp -p icmp --icmp-type port-unreachable -j ACCEPT ${FW} -A Icmp -p icmp --icmp-type host-unreachable -j ACCEPT ${FW} -A Icmp -p icmp --icmp-type protocol-unreachable -j ACCEPT ${FW} -A Icmp -p icmp --icmp-type source-quench -j ACCEPT ${FW} -A Icmp -p icmp --icmp-type time-exceeded -j ACCEPT ${FW} -A Icmp -p icmp --icmp-type parameter-problem -j ACCEPT ${FW} -A Icmp -p icmp -j DROP } function setupTables { typeset X Y Z typeset -A DUP print -u2 -f 'Init iptables: %T\n' if [[ -z ${DNS_SERVER} ]]; then # stupid ubuntu stuff puts it in several times while read X Y Z ; do [[ $X == 'nameserver' && -n $Y && -z ${DUP[$Y]} ]] && \ DNS_SERVER+=",$Y" && DUP[$Y]=1 done ${LOG}; RES=$? ;; stop) deleteTables 2>>${LOG} ; RES=$? ;; *) showUsage ; exit 1 ;; esac if [[ -t 2 && -s ${LOG} ]]; then X=( ${ wc -l ${LOG} ; } ) (( L == 0 )) && X=$(<${LOG}) || X=${ head -s $L ${LOG} ; } print -ru2 "$X" fi return ${RES} } function doInstall { typeset A X N D integer SYSV=0 RES=1 A=( ${ ls -l /sbin/init ; } ) X="${A[10]}" [[ ${X##*/} == 'systemd' ]] || SYSV=1 N=${SYSV_SCRIPT##*/} D=${.sh.match} if [[ -e ${SYSV_SCRIPT} ]]; then /usr/sbin/update-rc.d -f $N remove if [[ ! -d $D/OLD ]]; then mkdir $D/OLD || { print -u2 "${SYSV_SCRIPT} backup failed." ; } fi [[ -d $D/OLD && ! -e $D/OLD/$N ]] && mv ${SYSV_SCRIPT} $D/OLD/$N rm -f ${SYSV_SCRIPT} fi if (( SYSV )); then cp ${FPROG} ${SYSV_SCRIPT} && chmod 755 ${SYSV_SCRIPT} && RES=0 && \ /usr/sbin/update-rc.d $N defaults print -u2 "Run '${SYSV_SCRIPT} start' to start ipfilter immediately" \ 'or reboot the OS.' else [[ -d ${SYSTEMD_SCRIPT%/*} ]] || mkdir -p ${SYSTEMD_SCRIPT%/*} if cp ${FPROG} ${SYSTEMD_SCRIPT} ; then chmod 755 ${SYSTEMD_SCRIPT} && RES=0 showService >${SYSTEMD_SVC} chmod 644 ${SYSTEMD_SVC} /bin/systemctl daemon-reload /bin/systemctl enable $N print -u2 "Run '/bin/systemctl start $N' to start ipfilter" \ 'immediately or reboot the OS.' fi fi if [[ ! -e ${RULES_FILE} ]]; then showExample >${RULES_FILE} && chmod 644 ${RULES_FILE} fi return ${RES} } function copyOld { typeset N D N=${SYSV_SCRIPT##*/} D=${.sh.match} [[ -e $D/OLD/$N ]] && cp $D/OLD/$N ${RULES_FILE} && chmod 644 ${RULES_FILE} } USAGE='[-?1.0 ] [-copyright?Copyright (c) 2014 Jens Elkner. All rights reserved.] [-license?CDDL 1.0] [+NAME?'"${PROG}"' - script to setup or flush iptables.] [+DESCRIPTION?This script is used primarily by the systemd \biptables.service\b to start/stop iptables firewall. On non-systemd systems it might be installed as \b'"${SYSV_SCRIPT}"'\b and activated via the usual \bupdate-rc.d iptables \b{\bdefaults\b|\bremove\b} command.] [+?On start it sources in the \bksh93\b fragment \b'"${RULES_FILE}"'\b, which should provide the functions \binitGlobals\b, \bsetupExtChain\b, \bsetupExtIntChain\b, \bsetupIntExtChain\b and \bsetupIntChain\b. \binitGlobals\b should just contain definitions of environment variables, which should be visible to all functions of this script - just for convinience, to ease the setup. All other functions should just contain \biptables\b(8) calls to add rulles to the corresponding chains. The functions having \bInt\b in their name are called only, if the env var \bINTERNAL_IF\b is set. For convinience all functions may use the \b$FW\b instead of \b/sbin/iptables\b to get a more readable, less bloated rules file.] [h:help?Print this help and exit.] [F:functions?Print a list of all functions available.] [T:trace]:[functionList?A comma separated list of functions of this script to tr ace (convinience for troubleshooting).] [+?] [c:copy?Copies the old SysV init script to \b'"${RULES_FILE}"'\b (overwrites exting file unconditionally) for convinience (combinable with \b-i\b). It certainly needs to be adjusted, but makes migration easier and helps to free the brain wrt. old locations.] [i:install?Convinience option to install this script at the preferred location wrt. systemd or SysV init and generate an corresponding example rules file if there is not already one (see \bFILES\b). If \b'"${SYSV_SCRIPT}"'\b exists, it gets unregistered and moved to \b'"${SYSV_SCRIPT%/*}/OLD${.sh.match}"'\b unless there is such a file already. In this case the script gets simply deleted.] [l:lookup?Just lookup the IP addresses of the given operands using the name services of the running system and exit.] [r:rules?Print out a rules file example (which can be taken as a start for \b'"${RULES_FILE}"'\b) and exit.] [s:service?Print out the default service definition and exit.] [+FILES?]{ [+'"${SYSTEMD_SVC}"'?The iptables service definition.] [+'"${SYSTEMD_SCRIPT}"'?The preferred location of this script on systemd driven boxes.] [+'"${SYSV_SCRIPT}"'?The preferred location of this script on SysV driven boxes.] [+'"${RULES_FILE}"'?The file containing the rules to setup the IP tables.] [+'"${DEFAULT_LOG}"'?The default logfile for this script.] } [+SEE ALSO?\biptables\b(8), \bsystemctl\b(1).] \n\n{start|stop|refresh}|{IP...} ' X="${ print ${USAGE} ; }" integer LOOKUP=0 EXAMPLE=0 SVC=0 COPY=0 INSTALL=0 DEBUG= while getopts "${X}" OPT ; do case "${OPT}" in # common options h) showUsage MAIN ; exit 0 ;; F) typeset +f ; exit 0 ;; T) DEBUG+=" ${OPTARG} " ;; c) COPY=1 ;; i) INSTALL=1 ;; l) LOOKUP=1 ;; r) EXAMPLE=1 ;; s) SVC=1 ;; *) showUsage ; exit 0 ;; esac done X=$((OPTIND-1)) shift $X && OPTIND=1 unset X enableDebug (( LOOKUP )) && { getIpByName "$@" ; exit $? ; } (( EXAMPLE )) && { showExample "$@" ; exit $? ; } (( SVC )) && { showService "$@" ; exit $? ; } if (( INSTALL || COPY )); then SVC=0 (( INSTALL )) && { doInstall "$@" ; (( SVC+=$? )) ; } (( COPY )) && { copyOld "$@" ; (( SVC+=$? )) ; } exit ${SVC} fi doMain "$@"