#!/bin/bash #Copyright 2001, William Stearns #GPL'd #Version 0.2 #This script implements a shun list, a list of machines with which we #do not wish to talk. #Works on Linux kernel 2.2 (ipchains) and kernel 2.4 (iptables) #machines at the moment; kernel 2.0 (ipfwadm) doesn't support additional #chains (and hence the RETURN jump needed to support NeverShun, #so we'll have to get fancy to support it. #FIXME - Shun/NeverShun localnets? #FIXME - conditional functions. #FIXME - log+drop option #FIXME - verbose option #route -n | grep '^[0-9]' | grep -v 255.255.255.255 | grep -v '^0\.0\.0\.0' InChain="shunin" OutChain="shunout" NowStamp=`date +%s` #EST: 1001705964 #PST: 1001706023 Debug () { echo "$*" >/dev/stderr } if type -path iptables >/dev/null 2>&1 && iptables -L -n >/dev/null 2>&1 ; then #If iptables supported FwType=iptables elif [ -f /proc/net/ip_fwchains ] && type -path ipchains >/dev/null 2>&1 ; then #If ipchains supported FwType=ipchains else echo Neither ipchains nor iptables seems to be supported. Exiting. exit 1 fi NewChain () { #Create a user-defined chain if [ -z "$1" ]; then Debug Missing chain name in NewChain ; return 1 ; fi if [ "$FwType" = "ipchains" ]; then #If ipchains supported if ! ipchains -L $1 -n >/dev/null 2>&1 ; then #If chain doesn't exist ipchains -N $1 >/dev/null 2>&1 #Create it ipchains -I $InChain 1 -i lo -j RETURN #Never shun on the loopback interface ipchains -I $OutChain 1 -i lo -j RETURN fi elif [ "$FwType" = "iptables" ]; then #If iptables supported if ! iptables -L $1 -n >/dev/null 2>&1 ; then #If chain doesn't exist iptables -N $1 >/dev/null 2>&1 #Create it iptables -I $InChain 1 -i lo -j RETURN #Never shun on the loopback interface iptables -I $OutChain 1 -o lo -j RETURN fi fi } FlushOrNewChain () { #Create or clear a user-defined chain if [ -z "$1" ]; then Debug Missing chain name in FlushOrNewChain ; return 1 ; fi if [ "$FwType" = "ipchains" ]; then #If ipchains supported if ipchains -L $1 -n >/dev/null 2>&1 ; then #If chain exists ipchains -F $1 >/dev/null 2>&1 #Flush it else #else ipchains -N $1 >/dev/null 2>&1 #Create it fi elif [ "$FwType" = "iptables" ]; then #If iptables supported if iptables -L $1 -n >/dev/null 2>&1 ; then #If chain exists iptables -F $1 >/dev/null 2>&1 #Flush it else #else iptables -N $1 >/dev/null 2>&1 #Create it fi fi } ExemptLoopback () { if [ "$FwType" = "ipchains" ]; then #If ipchains supported #FIXME - check for existence first ipchains -I $InChain 1 -i lo -j RETURN #Never shun on the loopback interface ipchains -I $OutChain 1 -i lo -j RETURN elif [ "$FwType" = "iptables" ]; then #If iptables supported if ! iptables -L $InChain -n -v | grep -q '.*RETURN.* lo\W*0\.0\.0\.0/0' ; then iptables -I $InChain 1 -i lo -j RETURN #Never shun on the loopback interface fi if ! iptables -L $OutChain -n -v | grep -q '.*RETURN.* lo\W*0\.0\.0\.0/0' ; then iptables -I $OutChain 1 -o lo -j RETURN fi fi } NeverShun () { #this function accepts a {Network,Netmask} address that should NEVER be #blocked, such as trusted machines. It assumes #the calling function has already checked for timeouts, etc. while [ -n "$1" ]; do if [ "$FwType" = "ipchains" ]; then ipchains -I $InChain 1 -s $1 -j RETURN ipchains -I $OutChain 1 -d $1 -j RETURN elif [ "$FwType" = "iptables" ]; then iptables -I $InChain 1 -s $1 -j RETURN iptables -I $OutChain 1 -d $1 -j RETURN fi shift done } Shun () { #this function accepts Network/Netmask pairs to block. It assumes #the calling function has already checked for timeouts, etc. while [ -n "$1" ]; do if [ "$FwType" = "ipchains" ]; then ipchains -A $InChain -s $1 -j DENY ipchains -A $OutChain -d $1 -j DENY elif [ "$FwType" = "iptables" ]; then iptables -A $InChain -s $1 -j DROP iptables -A $OutChain -d $1 -j DROP fi shift done } LinkIn () { #For each of the following, if there's no jump to the shun list, add it in at the top of the firewall. if [ "$FwType" = "ipchains" ]; then if ! ipchains -L input -n | grep $InChain >/dev/null 2>&1 ; then ipchains -I input -j $InChain fi if ! ipchains -L forward -n | grep $InChain >/dev/null 2>&1 ; then ipchains -I forward -j $InChain fi if ! ipchains -L forward -n | grep $OutChain >/dev/null 2>&1 ; then ipchains -I forward -j $OutChain fi if ! ipchains -L output -n | grep $OutChain >/dev/null 2>&1 ; then ipchains -I output -j $OutChain fi elif [ "$FwType" = "iptables" ]; then if ! iptables -L INPUT -n | grep $InChain >/dev/null 2>&1 ; then iptables -I INPUT -j $InChain fi if ! iptables -L FORWARD -n | grep $InChain >/dev/null 2>&1 ; then iptables -I FORWARD -j $InChain fi if ! iptables -L FORWARD -n | grep $OutChain >/dev/null 2>&1 ; then iptables -I FORWARD -j $OutChain fi if ! iptables -L OUTPUT -n | grep $OutChain >/dev/null 2>&1 ; then iptables -I OUTPUT -j $OutChain fi fi } LocalIps () { /sbin/ifconfig 2>/dev/null | grep 'inet addr:' | sed -e 's/.*inet addr://' | awk '{print $1}' } ParseConfFile () { local ConfFile="$1" local LocalCopy="$2" #For an ftp or http URL in $1, we want a local filename where we can cache possibly outdated copies. local Command local Param1 local Param2 if [ -z "$ConfFile" ]; then Debug Empty ConfFile in ParseConfFile return 1 fi case "$ConfFile" in ftp:*|http:*) #Debug http downloading if [ -z "$LocalCopy" ]; then echo $0: Empty Local filename for \""$ConfFile"\" in ParseConfFile return 1 fi #Debug Caching "$ConfFile" at "$LocalCopy" #Use timestamping to verify new file, only wait 30 seconds, output to local filename if wget -N $ConfFile -T 30 -O $LocalCopy.tmp 2>/dev/null ; then Debug Successful wget download of "$ConfFile", saving as "$LocalCopy" mv -f $LocalCopy.tmp $LocalCopy ConfFile="$LocalCopy" else Debug Unsuccessful wget download of "$ConfFile" rm -f $LocalCopy.tmp if [ -f $LocalCopy ]; then Debug Using cached copy $LocalCopy ConfFile=$LocalCopy else Debug No local cache $LocalCopy available. ConfFile=/dev/null fi fi ;; *) : #Debug Straight local file processing ;; esac Debug Parsing configuration file "$ConfFile" if [ -f "$ConfFile" ]; then cat "$ConfFile" | sed -e 's/#.*//' | while read Command Param1 Param2 ; do #Debug read: $Command $Param1 $Param2 #FIXME - document textual commands such as shun, nevershun, source. case $Command in +|shun) if [ -n "$Param1" ]; then if [ -z "$Param2" ] || [ "$Param2" -gt $NowStamp ]; then Shun "$Param1" else Debug $Param1 expired - $Param2 le $NowStamp fi else Debug Missing address after '+' in \""$ConfFile"\" fi ;; .|source) if [ -n "$Param1" ]; then #FIXME - detect loops with list of files encountered ParseConfFile $Param1 $Param2 else Debug Missing file/URL after '.' in \""$ConfFile"\" fi ;; !|nevershun) if [ -n "$Param1" ]; then NeverShun "$Param1" else Debug Missing NeverShun address after '!' in \""$ConfFile"\" fi ;; "") : #Nothing to do, empty line ;; [1-9]*) Shun "$Command" ;; *) : #Don't want to trip on unrecognized lines, as commands may #be embedded in standard html files with lots of chaff. #Debug $0: Unrecognized command \""$Command"\" in file \""$ConfFile"\" >/dev/stderr ;; esac Command='' ; Param1='' ; Param2='' done else Debug Nonexistant ConfFile \""$ConfFile"\" in ParseConfFile fi } AddToShunList () { #$ConfFile $Action $OneIp... if [ -z "$1" ]; then Debug Empty Filename in AddToShunList return 1 fi if [ ! -f "$1" ]; then Debug Missing file $1 in AddToShunList return 1 fi if [ ! -w "$1" ]; then Debug Unwriteable file $1 in AddToShunList return 1 fi ShunFile=$1 shift if [ -z "$1" ]; then Debug empty action in AddToShunList return 1 fi Action=$1 shift while [ -n "$1" ]; do if ! cat $ShunFile 2>/dev/null | grep -q "^[^ ]* $1" ; then #The line is not already in the configuration file echo "$Action $1" >>$ShunFile fi shift done } NukeChain () { if [ "$FwType" = "ipchains" ]; then #If ipchains supported if ipchains -L $1 -n >/dev/null 2>&1 ; then #If chain exists ipchains -F $1 >/dev/null 2>&1 #Flush it ipchains -X $1 >/dev/null 2>&1 #Delete it fi elif [ "$FwType" = "iptables" ]; then #If iptables supported if iptables -L $1 -n >/dev/null 2>&1 ; then #If chain exists iptables -F $1 >/dev/null 2>&1 #Flush it iptables -X $1 >/dev/null 2>&1 #Delete it fi fi } #Parse CLP's ConfFile="/etc/shun/shun.conf" if [ ! -d /etc/shun ]; then mkdir -p /etc/shun fi while [ -n "$1" ]; do case "$1" in start) Action=start ;; stop) Action=stop ;; +|shun) if [ -n "$2" ]; then IpsToShun="$IpsToShun $2" shift else echo $0: "$1 specified, but no IP address given" >/dev/stderr fi ;; [1-9]*) IpsToShun="$IpsToShun $1" ;; !|nevershun) if [ -n "$2" ]; then IpsToNeverShun="$IpsToNeverShun $2" shift else echo $0: "$1 specified, but no IP address given" >/dev/stderr fi ;; -c|.|source) if [ -n "$2" ]; then ConfFile="$2" shift case "$ConfFile" in ftp:*|http:*) if [ -n "$2" ]; then Debug Saving a local copy of \""$ConfFile"\" in \""$2"\" LocalCopy="$2" shift else Debug URL \""$ConfFile"\" requested, but no local cache filename given. Please supply one. LocalCopy=/etc/shun/cachecopy fi ;; *) if [ ! -e $ConfFile ]; then echo $0: "Configuration file $2 does not exist." >/dev/stderr fi ;; esac else echo $0: "$1 specified, but no Configuration file or URL given" >/dev/stderr fi ;; *) echo $0: Unrecognized parameter "$1" >/dev/stderr ;; esac shift done if [ -n "$IpsToShun$IpsToNeverShun" ]; then if [ ! -f $ConfFile ]; then touch $ConfFile fi NewChain $InChain NewChain $OutChain ExemptLoopback LinkIn for OneIp in $IpsToShun ; do Shun $OneIp AddToShunList $ConfFile '+' $OneIp done for OneIp in $IpsToNeverShun ; do NeverShun $OneIp AddToShunList $ConfFile '!' $OneIp done fi case $Action in start) #Start actual shun list setup FlushOrNewChain $InChain FlushOrNewChain $OutChain ExemptLoopback LinkIn for OneIp in `LocalIps` ; do Shun $OneIp done ParseConfFile "$ConfFile" if [ ! -d /var/run ]; then mkdir -p /var/run fi touch /var/run/shun.pid ;; stop) NukeChain $InChain NukeChain $OutChain if [ -d /var/run ]; then rm -f /var/run/shun.pid fi ;; "") : #It actually is legal to have a null action. #/etc/init.d/shun + 192.168.12.0/24 #will add that network to a firewall that already has the appropriate chains ;; *) Debug Unrecognized action \"$Action\" ;; esac