#!/bin/bash #originally spamhaus drop script. #modified by Hanz Makmur 2015-03-13 #using LCSR DROP file add to iptables # locking added by daw; 9/14/16 # logger added by daw; 10/2/17 # efficiency improvement: instead of flushing and re-adding entire list, # only delete expired entries and add new ones -- daw, 10/23/17 # switch to using curl, iptables-{save,restore}; 4/25/18 # syslog "version";, refuse to run if not on public internet; 10/11/18 # check status after iptables commands; syslog potential reason on file retrieval failure; # defend against iptables-restore errors; 10/12/18 # begin ipset mod (indicate presence or absence of ipset in SUMN); 10/19/18 # continue ipset mod (maintain LCSRDrop ipset); 10/23/18 # finish ipset mod (if ipset present, remove LCSRDrop chain, don't maintain LCSRDrop chain, # and add LOG_AND_DROP chain using LCSRDrop ipset); 11/7/18 # ipset might be in /sbin rather than /usr/sbin; 11/12/18 # insert LOG_AND_DROP chain at beginning rather then end; 11/12/18 # use wget if curl is not available; 12/7/18 # save temp copy of iptables -L for debugging if removing chain in favor of ipset; 12/9/18 # save a copy of ipset restore file if ipset restore fails; 12/11/18 # use timeout for curl/wget if available; 1/17/19 # avoid buggy timeout on Fedora Core 3 (dhcp2.srv.lcsr) ; 1/22/19 # workaround for buggy timeout on Fedora Core 3 (dhcp2.srv.lcsr) ; 1/23/19 # don't assume LOG_AND_DROP chain exists if LCSRDrop ipset does ; 4/21/19 # install ipset if needed and available ; 4/25/19 # add "-exist" to ipset del commands ; 4/25/19 # make sure 0 return status from yum info means ipset is available ; 4/26/19 # "yum info" needs "timeout -s 9" on Fedora Core 3 (dhcp2.srv.lcsr) ; 4/26/19 # avoid flurry of errors due to simultaneous "iptables restart" ; 4/27/19 # drop previous edit -- reschedule or eliminate "iptables restart" ; 5/7/19 # fix apt/apt-get code; 8/12/19 # add a clue when refusing to run; 8/13/19 # remove s from https because old SSL library ; 6/23/21 # path to iptables export PATH=$PATH:/sbin # sbin needed fot apt on klinzhai.rutgers.edu IPTABLES="/sbin/iptables"; IPSAVE="/sbin/iptables-save"; IPRESTORE="/sbin/iptables-restore"; if [ -e /usr/sbin/ipset ]; then IPSET=/usr/sbin/ipset ; fi if [ -e /sbin/ipset ]; then IPSET=/sbin/ipset ; fi CURL="`which curl`"; WGET="`which wget`"; if [ -e /usr/bin/timeout ]; then TIMEOUT=/usr/bin/timeout ; fi if [ -e /bin/timeout ]; then TIMEOUT=/bin/timeout ; fi if [ $TIMEOUT"x" != "x" ]; then TIMEOUT9="$TIMEOUT -s 9 15" TIMEOUT="$TIMEOUT 15" fi # if timeout is broken here, don't use it # (switch within command confuses timeout on FC3) # I found a workaround. Put command into a file #$TIMEOUT echo x -x > /dev/null 2>&1 #if [ $? -ne 0 ]; then TIMEOUT="" ; fi LOGGER="`which logger`"; BASENAME="`which basename`"; BASE="`$BASENAME $0`"; TMP="/tmp/$BASE.$$"; NOIPSETF=/tmp/$BASE.noipset DQ='"' SUM=`which sum` # use SUMSEP to indicate whether using ipset SUMSEP='-' if [ $IPSET"x" != "x" ]; then SUMSEP="+"; fi SUMN=`$SUM $0 | sed "s; *;$SUMSEP;"` BASEDATE=`grep '; *[0-9]*/[0-9]*/[0-9]* *$' $0 | tail -1 | awk '{print $NF}'` VFILE=/tmp/$BASE.version # list of known IPs URL="http://report.rutgers.edu/DROP/attackers"; # save local copy here FILE="/tmp/attackers.drop" # iptables custom chain CHAIN="LCSRDrop"; # ipset custom set SETNAME="LCSRDrop"; # lockfile LOCKF=/tmp/lcsrdrop.lock # create temp lockfile ( echo $$ > $LOCKF.$$ ) > /dev/null 2>&1 # put that in real lockfile place of that doesn't exist already if [ ! -f $LOCKF ]; then /bin/mv $LOCKF.$$ $LOCKF > /dev/null 2>&1 fi # see who now has the lock if [ -f $LOCKF ]; then if [ -r $LOCKF ]; then LPID=`cat $LOCKF` else LPID=1 # if we cannot read file, use init's pid fi else LPID=1 # if we cannot create file, use init's pid fi # if it's not us, try removing lockfile if that process no longer exists # and clean up after self if [ $$ != "$LPID" ]; then /bin/ps -p $LPID > /dev/null if [ $? -ne 0 ]; then /bin/rm -f $LOCKF > /dev/null 2>&1 fi /bin/rm -f $LOCKF.$$ exit 1 fi # Check if version changed. If so, log it if [ ! -e $VFILE ]; then touch $VFILE ; fi echo "$SUMN ($BASEDATE)" > $VFILE.new diff $VFILE $VFILE.new > /dev/null if [ $? -ne 0 ]; then LOGVERSION=1 else LOGVERSION=0 fi # If the date has changed, log the version as well DN=`/bin/ls -l $VFILE $VFILE.new | awk '{print $6,$7}' | sort -u | wc -l` if [ $DN -ne 1 ]; then LOGVERSION=1 ; fi /bin/mv -f $VFILE.new $VFILE if [ $LOGVERSION -eq 1 ]; then $LOGGER -t$BASE -pdaemon.err "SUMN=$SUMN ($BASEDATE)" echo `date` "SUMN=$SUMN ($BASEDATE)" fi # commented R private net assert to test : <<'END' /sbin/ip addr show | egrep "128\.6\.|165\.230\." > /dev/null if [ $? -ne 0 ] && [ ! -e $0.run-in-private-IP-space ]; then # $LOGGER -t$BASE -pdaemon.err "refusing to run in private IP space" IP=`ifconfig -a | grep 'inet 172\.' | sed -e 's;.* inet ;;' -e 's; .*;;'` $LOGGER -t$BASE -pdaemon.err "refusing to run in private IP space ($IP)" echo $BASE need not be run on Rutgers private IP space exit 0 fi END # make sure ipset is installed if possible if [ $IPSET"x" == "x" -a ! -e $NOIPSETF ]; then if [ -e /usr/bin/apt ]; then APT=/usr/bin/apt ; fi if [ -e /bin/apt ]; then APT=/bin/apt ; fi # if [ -e /usr/bin/apt-get ]; then APTGET=/usr/bin/apt-get ; fi # if [ -e /bin/apt-get ]; then APTGET=/bin/apt ; fi if [ -e /usr/bin/yum ]; then YUM=/usr/bin/yum ; fi if [ -e /bin/yum ]; then YUM=/bin/yum ; fi if [ $APT"x" == "x" -a $YUM"x" == "x" ]; then $LOGGER -t$BASE -pdaemon.err "Neither apt nor yum found" fi # if [ $APT"x" != "x" -a $APTGET"x" != "x" ]; then if [ $APT"x" != "x" ]; then $APT show ipset > /dev/null 2>&1 if [ $? -eq 0 ]; then # echo `date +%T` "Installing ipset using apt-get" # $LOGGER -t$BASE -pdaemon.err "Installing ipset using apt-get" # $APTGET install -y ipset echo `date +%T` "Installing ipset using apt" $LOGGER -t$BASE -pdaemon.err "Installing ipset using apt" # Avoid sometimes exiting install with status 100 $APT update STATUS=$? if [ $STATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$APT update returned status $STATUS" fi $APT install -y ipset STATUS=$? if [ $STATUS -ne 0 ]; then # $LOGGER -t$BASE -pdaemon.err "$APTGET install -y ipset returned status $STATUS" $LOGGER -t$BASE -pdaemon.err "$APT install -y ipset returned status $STATUS" else if [ -e /usr/sbin/ipset ]; then IPSET=/usr/sbin/ipset ; fi if [ -e /sbin/ipset ]; then IPSET=/sbin/ipset ; fi fi else touch $NOIPSETF fi fi if [ $YUM"x" != "x" ]; then $TIMEOUT9 $YUM info ipset > $TMP 2>&1 if [ $? -eq 0 ]; then grep ipset $TMP /dev/null 2>&1 if [ $? -eq 0 ]; then echo `date +%T` "Installing ipset using yum" $LOGGER -t$BASE -pdaemon.err "Installing ipset using yum" $YUM install -y ipset STATUS=$? if [ $STATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$YUM install -y ipset returned status $STATUS" else if [ -e /usr/sbin/ipset ]; then IPSET=/usr/sbin/ipset ; fi if [ -e /sbin/ipset ]; then IPSET=/sbin/ipset ; fi fi else touch $NOIPSETF fi else touch $NOIPSETF fi fi fi # check to see if the chain already exists $IPTABLES -L $CHAIN -n > /dev/null 2>&1 ILCSTATUS=$? if [ $ILCSTATUS -ne 0 ]; then # only create chain if ipset not present if [ $IPSET"x" == "x" ]; then echo `date +%T` "Chain $CHAIN not detected. Creating new chain...." # create a new chain set $IPTABLES -N $CHAIN STATUS=$? if [ $STATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$IPTABLES -N $CHAIN returned status $STATUS" fi # tie chain to input rules so it runs $IPTABLES -A INPUT -j $CHAIN STATUS=$? if [ $STATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$IPTABLES -A INPUT -j $CHAIN returned status $STATUS" fi # don't allow this traffic through $IPTABLES -A FORWARD -j $CHAIN STATUS=$? if [ $STATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$IPTABLES -A FORWARD -j $CHAIN returned status $STATUS" fi fi else # chain exists -- remove it if ipset present if [ $IPSET"x" != "x" ]; then # Save an old copy of this for debugging, just in case... echo `date +%T` "Saving old copy of $IPSET -L -n" $LOGGER -t$BASE -pdaemon.err "Saving old copy of $IPSET -L -n" $IPTABLES -L -n > /tmp/$BASE.iptables-L-n 2>&1 echo `date +%T` "$IPSET exists. Removing chain $CHAIN...." $IPTABLES -D FORWARD -j $CHAIN STATUS=$? if [ $STATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$IPTABLES -D FORWARD -j $CHAIN returned status $STATUS" fi $IPTABLES -D INPUT -j $CHAIN STATUS=$? if [ $STATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$IPTABLES -D INPUT -j $CHAIN returned status $STATUS" fi $IPTABLES -F $CHAIN STATUS=$? if [ $STATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$IPTABLES -F $CHAIN returned status $STATUS" fi $IPTABLES -X $CHAIN STATUS=$? if [ $STATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$IPTABLES -X $CHAIN returned status $STATUS" fi fi fi; # Create LCSRDrop ipset if it does not exist if [ $IPSET"x" != "x" ]; then $IPSET list $SETNAME > /dev/null 2>&1 if [ $? -ne 0 ]; then echo `date +%T` "set $SETNAME not detected. Creating new set...." # create new ipset set $IPSET create $SETNAME iphash STATUS=$? if [ $STATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$IPSET create $SETNAME iphash returned status $STATUS" fi fi # Make sure LOG_AND_DROP chain exists # $IPTABLES -L LOG_AND_DROP -n > /dev/null 2>&1 # On report.cs at 0700, this sometimes does not detect LOG_AND_DROP because of simultaneous "iptables restart" # No simple way to avoid this other than rescheduling (or removing) "iptables restart" $IPTABLES -L LOG_AND_DROP -n > $TMP 2>&1 if [ $? -ne 0 ]; then echo " error from $IPTABLES -L LOG_AND_DROP -n:" sed 's;.; &;' $TMP echo `date +%T` "Chain LOG_AND_DROP not detected. Creating new chain...." # create LOG_AND_DROP chain $IPTABLES -N LOG_AND_DROP STATUS=$? if [ $STATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$IPTABLES -N LOG_AND_DROP returned status $STATUS" fi $IPTABLES -A LOG_AND_DROP -m limit --limit 10/min -j LOG --log-prefix "LCSRDropped: " STATUS=$? if [ $STATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$IPTABLES -A LOG_AND_DROP -m limit --limit 10/min -j LOG --log-prefix ${DQ}LCSRDropped: $DQ returned status $STATUS" fi $IPTABLES -A LOG_AND_DROP -j REJECT STATUS=$? if [ $STATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$IPTABLES -A LOG_AND_DROP -j REJECT returned status $STATUS" fi # $IPTABLES -A INPUT -m set --match-set $SETNAME src -j LOG_AND_DROP # Put this at the beginning rather than the end $IPTABLES -I INPUT -m set --match-set $SETNAME src -j LOG_AND_DROP STATUS=$? if [ $STATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$IPTABLES -I INPUT -m set --match-set $SETNAME src -j LOG_AND_DROP returned status $STATUS" fi fi fi # make sure $FILE does not exist if [ -e $FILE ]; then /bin/mv -f $FILE $FILE.old fi; # get a copy of the drop list echo `date +%T` "Retrieving copy of attackers data...." if [ $CURL"x" != "x" ]; then # $TIMEOUT $CURL -k -s $URL -o $FILE # Put command into file so buggy timeout won't see it under FC3 echo $CURL -k -s $URL -o $FILE > $TMP $TIMEOUT /bin/sh $TMP CSTATUS=$? else # if curl doesn't exist, use wget $TIMEOUT $WGET -q --no-check-certificate -O $FILE $URL CSTATUS=$? fi # make sure it has nothing but IPs grep -v '^[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*$' $FILE > /dev/null CJSTATUS=$? # check to see if curl succeeded and did not get junk if [ $CSTATUS -eq 0 -a $CJSTATUS -eq 1 ]; then /bin/rm -f $FILE.old else # use old data /bin/mv -f $FILE $FILE.fail /bin/mv -f $FILE.old $FILE STRING=` grep '' $FILE.fail | sed -e 's;.*<title>;;' -e 's:<.*:; :'` echo `date +%T` "Retrieval of attackers data failed. Reusing old data...." $LOGGER -t$BASE -pdaemon.err "Retrieval of attackers data failed ($STRING$CSTATUS,$CJSTATUS). Reusing old data...." fi; # Maintain LCSRDrop ipset if ipset program exists if [ $IPSET"x" != "x" ]; then $IPSET list $SETNAME > $TMP.ipset 2>&1 STATUS=$? if [ $STATUS -ne 0 ]; then echo `date +%T` "$IPSET list $SETNAME returned status $STATUS" $LOGGER -t$BASE -pdaemon.err "$IPSET list $SETNAME returned status $STATUS" fi grep '^[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*$' $TMP.ipset | sort -u > $TMP.old sort -u $FILE > $TMP.new comm -23 $TMP.{old,new} > $TMP.remove comm -13 $TMP.{old,new} > $TMP.add sed "s;.;del -exist $SETNAME &;" $TMP.remove > $TMP.restore sed "s;.;add -exist $SETNAME &;" $TMP.add >> $TMP.restore if [ -s $TMP.restore ]; then echo `date +%T` "restoring new $SETNAME ipset...." $IPSET restore < $TMP.restore STATUS=$? if [ $STATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$IPSET restore < $TMP.restore returned status $STATUS" echo `date +%T` "Saving copy of failed restore file" $LOGGER -t$BASE -pdaemon.err "Saving copy of failed restore file" /bin/mv $TMP.restore /tmp/$BASE.ipset.restore.failed fi else echo `date +%T` "No changes $SETNAME ipset...." fi fi # Old code maintains LCSRDrop chain if ipset not present if [ $IPSET"x" == "x" ]; then # save current iptables rules with/without LCSRDRop $IPSAVE > $TMP.old SSTATUS=$? if [ $SSTATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$IPSAVE > $TMP.old returned status $SSTATUS" fi $IPSAVE | grep -v '^-A LCSRDrop' > $TMP STATUS=$? if [ $STATUS -ne 0 ]; then $LOGGER -t$BASE -pdaemon.err "$IPSAVE | grep -v '^-A LCSRDrop' > $TMP returned status $STATUS" fi # build new save file to load CLINE=`grep -n "COMMIT" $TMP | tail -1 | sed 's;:.*;;'` (( CLINEm1=$CLINE-1 )) head -$CLINEm1 $TMP > $TMP.new sed 's;.*;-A LCSRDrop -s &/32 -j DROP;' $FILE >> $TMP.new tail --lines=+$CLINE $TMP >> $TMP.new diff $TMP.{old,new} > /dev/null if [ $? -ne 0 ]; then echo `date +%T` "restoring new iptables rules...." # "restore" new iptables rules $IPRESTORE < $TMP.new STATUS=$? if [ $STATUS -ne 0 ]; then echo `date +%T` "restore failed" $LOGGER -t$BASE -pdaemon.err "$IPRESTORE < $TMP.new returned status $STATUS" if [ $SSTATUS -eq 0 ]; then echo " restoring to saved state" $LOGGER -t$BASE -pdaemon.err "Restoring to saved state" $IPRESTORE < $TMP.old STATUS=$? if [ $STATUS -ne 0 ]; then echo `date +%T` "that restore failed too" $LOGGER -t$BASE -pdaemon.err "$IPRESTORE < $TMP.old returned status $STATUS" fi fi fi else echo `date +%T` "No changes in iptables rules...." fi; fi # remove lockfile (and possible leftover temp lockfile) and temp files /bin/rm -f $LOCKF $LOCKF.$$ $TMP*