457 lines
15 KiB
Bash
Executable File
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*
|