agmission/Others/scripts/lcsrdrop.sh

457 lines
15 KiB
Bash
Executable File

#!/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 '<title>' $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*