#!/bin/bash
#
# iptables-country-block  Add country blocking rules to iptables firewall
#
# chkconfig: 2345 09 91
# description:	Adds and removes country blocking rules to iptables firewall
#
# config: /etc/sysconfig/iptables-cblock-config
#
# Origin from http://www.cyberciti.biz/faq/block-entier-country-using-iptables
# Modified by David Picard of PSInd, LLC - dpicard [a-t] psind [d-ot] com - to
# include ability to cache the address list, create chains by subnet thus 
# optimizing rule execution, clean up the rules without wiping the existing 
# tables, include port overrides to allow traffic deemed 'safe', and leverage 
# iptables-restore to optimize rules loading.

# Source function library.
. /etc/init.d/functions

# The following values may be overridden in the config
### Block all traffic from AFGHANISTAN (af), CHINA (CN) and KOREA(kr).
### Use ISO code ###
ISO="af cn kr" 
IPTCBRESTORE="/etc/sysconfig/iptables.cb"
IPTCBDEVICE=eth0
ALLOWPORTS=80,443
ALLOWSUBNET=192.168.0.0/255.255.0.0
MAXZONEAGE=7
DLROOT="http://www.ipdeny.com/ipblocks/data/countries"
IPTABLES_VERBOSE="no"
IPTABLES_STATUS_NUMERIC="yes"
IPTABLES_STATUS_VERBOSE="yes"
IPTABLES_STATUS_LINENUMBERS="yes"

### Set PATH ###
IPT=/sbin/iptables
WGET=/usr/bin/wget
EGREP=/bin/egrep
IPTABLES_CB_CONFIG=/etc/sysconfig/iptables-cblock-config
 
### No editing below ###
IPTABLESCB=`basename $0`
CBLIST="countrydrop"
IPTCBLOG=/var/log/iptables_country_block.log
IPTCBLOAD=`mktemp`
IPTCBTMP=`mktemp`
ZONEROOT="/var/iptables"
 
# Load country blocking firewall configuration.
[ -f "$IPTABLES_CB_CONFIG" ] && . "$IPTABLES_CB_CONFIG"

cleanOldRules(){
    $IPT -L $CBLIST > /dev/null 2>&1
    if [ $? = 0 ] ; then
	$IPT -D INPUT ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -j $CBLIST
	$IPT -D FORWARD ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -j $CBLIST
	$IPT -D OUTPUT ${IPTCBDEVICE:+-o }${IPTCBDEVICE} -j $CBLIST
    fi
    $IPT -F $CBLIST
    $IPT -X $CBLIST

    for i  in `$IPT -L -n | grep Chain | cut -f 2 -d ' ' | grep '\-$CBLIST'`
    do
	$IPT -F ${i}
	$IPT -X ${i}
    done
}

updateZoneFiles() {
    ZONEARCH=${ZONEROOT}/arch
    mkdir -p ${ZONEARCH}
    find ${ZONEROOT} -maxdepth 1 -mindepth 1 -ctime +${MAXZONEAGE} -exec mv {} ${ZONEARCH} \;

    for c  in $ISO
    do
	# local zone file
	tDB=$ZONEROOT/$c.zone
	
	if [ -f $tDB ] ; then
	    printf "Zone file %s is new enough - no update required.\n" $tDB >>${IPTCBLOG}
	else
	    # get fresh zone file if it is newer than MAXZONEAGE days
	    $WGET -O $tDB $DLROOT/$c.zone
	fi
    done
    oldzones=`find ${ZONEROOT} -mindepth 1 -maxdepth 1 -type f -exec basename {} \; | cut -f 1 -d '.'`
    # Archive old zones no longer blocked
    for z in $oldzones ; do
	archme=${c}
	for c  in $ISO ; do
	    if [ $c = $z ] ; then archme="X"; fi
	done
	if [ $archme = $z ] ; then
	    mv ${archme} ${ZONEARCH}
	else
	    printf "Working from previous zone file for %s\n" ${z} >>${IPTCBLOG}
	fi
    done
}
 
createIPTLoadFile() {
    printf "# Generated by %s on" $0 > ${IPTCBLOAD}
    printf "%s " `date` >> ${IPTCBLOAD}
    printf "\n*filter\n" >> ${IPTCBLOAD}
    # Create CBLIST chain
    printf ":$CBLIST - [0:0]\n" >> ${IPTCBLOAD}
    printf "%s INPUT ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -j $CBLIST\n" "-I" > ${IPTCBTMP}
    printf "%s OUTPUT ${IPTCBDEVICE:+-o }${IPTCBDEVICE} -j $CBLIST\n" "-I"  >> ${IPTCBTMP}
    printf "%s FORWARD ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -j $CBLIST\n" "-I" >> ${IPTCBTMP}

    printf "%s $CBLIST -m state --state RELATED,ESTABLISHED -j ACCEPT\n" "-I">> ${IPTCBTMP}
    if [ "Z${ALLOWPORTS}" = "Z" ] ; then
	printf "Blocking all traffic from country - no ports allowed\n" >>${IPTCBLOG}
    else
	printf "%s $CBLIST -p tcp -m multiport --dports ${ALLOWPORTS} -j RETURN\n" "-I">> ${IPTCBTMP}
    fi

    if [ "Z${ALLOWSUBNET}" = "Z" ] ; then
	printf "Blocking all traffic from country - no subnets excluded\n" >>${IPTCBLOG}
    else
	printf "%s $CBLIST -s ${ALLOWSUBNET} -j RETURN\n" "-I">> ${IPTCBTMP}
    fi

    for c  in $ISO
    do
	# local zone file
	tDB=$ZONEROOT/$c.zone
	
	# country specific log message
	SPAMDROPMSG="iptables: ${c}-Country-Drop: "
	
        # Create drop chain for identified packets
	CBLISTDROP=${c}-${CBLIST}-DROP
	printf ":${CBLISTDROP} - [0:0]\n" >> ${IPTCBLOAD}
	printf "%s ${CBLISTDROP} -j LOG --log-prefix \"$SPAMDROPMSG\"\n" "-A" >> ${IPTCBTMP}
	printf "%s ${CBLISTDROP} -j DROP\n" "-A" >> ${IPTCBTMP}
    
	# Load IP ranges into chains correlating to first octet
	BADIPS=$(egrep -v "^#|^$" $tDB)
	for ipblock in $BADIPS
	do
	    topip=`echo $ipblock | cut -f 1 -d '.'`
	    chainExists=`grep -c :${topip}-${CBLIST} ${IPTCBLOAD}`
	    if [ $chainExists = 0 ] ; then
		printf "Creating chain for octet %s\n" ${topip} >>${IPTCBLOG}
		printf ":$topip-$CBLIST - [0:0]\n" >> ${IPTCBLOAD}
		sip=${topip}.0.0.0/8
		printf "%s $CBLIST -s ${sip} -j $topip-$CBLIST\n" "-A" >> ${IPTCBTMP}
	    fi
	    printf "  Adding rule for %s to chain for octet %s\n" ${ipblock} ${topip} >>${IPTCBLOG}
	    printf "%s $topip-$CBLIST -s $ipblock -j ${CBLISTDROP}\n" "-A" >> ${IPTCBTMP}
	done
    done
    cat ${IPTCBTMP} >> ${IPTCBLOAD} && rm -f ${IPTCBTMP}
    printf "COMMIT\n# Completed on " >> ${IPTCBLOAD}
    printf "%s " `date` >> ${IPTCBLOAD}
    printf "\n" >> ${IPTCBLOAD}
}

directLoadTables() {
    # Create CBLIST chain
    $IPT -N $CBLIST
    # Block inbound packets on specified network device
    $IPT -I INPUT ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -j $CBLIST
    # The following rules are useful only in multi-NIC systems / gateways
    $IPT -I FORWARD ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -j $CBLIST
    $IPT -I OUTPUT ${IPTCBDEVICE:+-o }${IPTCBDEVICE} -j $CBLIST

    if [ "Z${ALLOWPORTS}" = "Z" ] ; then
	printf "Blocking all traffic from country - no ports allowed\n" >>${IPTCBLOG}
    else
	$IPT -I $CBLIST -p tcp -m multiport --dports ${ALLOWPORTS} -j RETURN
    fi

    if [ "Z${ALLOWSUBNET}" = "Z" ] ; then
	printf "Blocking all traffic from country - no subnets allowed\n" >>${IPTCBLOG}
    else
	$IPT -I $CBLIST -s ${ALLOWSUBNET} -j RETURN
    fi

    for c  in $ISO
    do
	# local zone file
	tDB=$ZONEROOT/$c.zone
	
	# country specific log message
	SPAMDROPMSG="$c Country Drop"
	
        # Create drop chain for identified packets
	CBLISTDROP=${c}-${CBLIST}-DROP
	$IPT -N ${CBLISTDROP}
	$IPT -A ${CBLISTDROP} -j LOG --log-prefix "$SPAMDROPMSG"
	$IPT -A ${CBLISTDROP} -j DROP
    
	# Load IP ranges into chains correlating to first octet
	BADIPS=$(egrep -v "^#|^$" $tDB)
	for ipblock in $BADIPS
	do
	    topip=`echo $ipblock | cut -f 1 -d '.'`
	    $IPT -L $topip-$CBLIST > /dev/null 2>&1
	    if [ $? = 1 ] ; then
		printf "Creating chain for octet %s\n" ${topip} >>${IPTCBLOG}
		$IPT -N $topip-$CBLIST
		sip=${topip}.0.0.0/8
		$IPT -A $CBLIST -s ${sip} -j $topip-$CBLIST
	    fi
	    printf "  Adding rule for %s to chain for octet %s\n" ${ipblock} ${topip} >>${IPTCBLOG}
	    $IPT -A $topip-$CBLIST -s $ipblock -j ${CBLISTDROP}
	done
    done
}

loadTables() {
    createIPTLoadFile
    ${IPT}-restore -n ${IPTCBLOAD}
    if [ ! "z${IPTCBRESTORE}" = "z" ] ; then
	RESTORE_DIR=`dirname ${IPTCBRESTORE}`
	[ -d ${RESTORE_DIR} ] && mv -f ${IPTCBLOAD} ${IPTCBRESTORE}
    fi
    #directLoadTables
    printf "Country block instituted for: %s\n" "$ISO" >>${IPTCBLOG}
}

status() {
    NUM=
    [ "x$IPTABLES_STATUS_NUMERIC" = "xyes" ] && NUM="-n"
    VERBOSE= 
    [ "x$IPTABLES_STATUS_VERBOSE" = "xyes" ] && VERBOSE="--verbose"
    COUNT=
    [ "x$IPTABLES_STATUS_LINENUMBERS" = "xyes" ] && COUNT="--line-numbers"

    for table in "filter" ; do
	echo $"Table: $table"
	$IPT -t $table --list $CBLIST $NUM $VERBOSE $COUNT
    done

    return 0
}

stop() {
    echo -n $"${IPTABLESCB}: Removing cb firewall rules: "

    # clean old rules
    cleanOldRules

    if [ $? -eq 0 ]; then
	success; echo
    else
	failure; echo; return 1
    fi
}

start() {
    echo -n $"${IPTABLESCB}: Applying cb firewall rules: "

   # create a dir
    [ ! -d $ZONEROOT ] && /bin/mkdir -p $ZONEROOT
 
    # update zone files as needed
    updateZoneFiles

    # create a new iptables list
    loadTables

    if [ $? -eq 0 ]; then
	success; echo
    else
	failure; echo; return 1
    fi
}

restart() {
    stop
    start
}

#[ ! "x$IPTABLES_VERBOSE" = "xyes" ] && exec 1>/dev/null
case "$1" in
    start)
	[ -f "$VAR_SUBSYS_IPTABLES" ] && exit 0
	start
	RETVAL=$?
	;;
    stop)
	[ "x$IPTABLES_SAVE_ON_STOP" = "xyes" ] && save
	stop
	RETVAL=$?
	;;
    restart|force-reload)
	restart
	RETVAL=$?
	;;
    status)
	status
	RETVAL=$?
	;;
    *)
	echo $"Usage: ${IPTABLESCB} {start|stop|restart|condrestart|status}"
	RETVAL=2
	;;
esac

exit $RETVAL
