rdt What does it do? Well, it scans a log file (/var/log/secure, or /var/log/auth.log in my case) for attempts to login (using 'tail -f') and (based upon configuration of rates and such) adds a rule to the iptables filter to completely DROP all packets from the IP address. And (after a configurable time - default of 1 hour) then deletes that rule to allow traffic.
I have polished this package up a little and posted a description here: ip-drop - JBR
#!/bin/sh
# This line continues for Tcl, but is a single line for 'sh' \
exec tclsh "$0" ${1+"$@"}
########################################################################
# tcl/tk code follows:
#=======================================================================
# Configuration:
#-----------------------------------------------------------------------
# set the maximum number of attempts for various types:
# iuf = "Illegal User From"
# fpi = "Failed Password from Illegal user"
# fpu = "Failed Password from User"
# fpr = "Failed Password from Root"
array set cfg { iuf 4 fpi 3 fpu 9 fpr 2 }
#-----------------------------------------------------------------------
# set how long an IP should remain DROPed:
array set cfg { delay 1h }
#-----------------------------------------------------------------------
# establish hosts / IP's that should NEVER be dropped (see ckIgnore):
# read a file for values:
#set cfg(ignore) /root/ad/ad.ign
# provide a list of hostnames/IP's:
#set cfg(ignore) [list host.example.com 192.168.144.120 ]
#-----------------------------------------------------------------------
# define the log file to scan for input:
set ipt(file) "/var/log/secure"
# define the log file to write:
set ipt(log) "/var/log/autoDrop"
#=======================================================================
# Colors (used for debugging):
# 0 is black, 1 is red, 2 is green, 3 is yellow,
# 4 is blue, 5 is magenta, 6 is cyan, 7 is white.
set ipt(norm) "\033\[0m" ;# normal
set ipt(dbg) "\033\[34m" ;# debug in blue
set ipt(dat) "\033\[36m" ;# data in cyan
set ipt(err) "\033\[0;31m" ;# error in red
#=======================================================================
if 0 { set DEBUG 1
set ipt(file) "./secure"
set ipt(log) "./log"
}
########################################################################
proc Debug {beg str} {
global ipt
if {[info exists ::DEBUG]} {
puts stderr "$ipt($beg)$str$ipt(norm)"
}
} ;# end of Debug
#-----------------------------------------------------------------------
proc error {str} {Debug err $str}
proc debug {str} {Debug dbg $str}
proc data {str} {Debug dat $str}
#-----------------------------------------------------------------------
proc log {str} {
global ipt
set dt [clock format [clock seconds] -format "%h %d %T" -gmt 0]
set fh [open $ipt(log) a]
puts $fh "$dt $str"
catch {close $fh}
} ;# end of log
#-----------------------------------------------------------------------
# Allow the ms parameter to end with s for seconds, m for minutes,
# h for hours, & d for days.
proc After {ms args} {
if {[regexp {^(\d+)([smhd])} $ms -> ms suffix]} {
switch -exact -- $suffix {
s {set ms [expr $ms * 1000]}
m {set ms [expr $ms * 1000*60]}
h {set ms [expr $ms * 1000*60*60]}
d {set ms [expr $ms * 1000*60*60*24]}
}
}
after $ms $args
} ;# end of After
#=======================================================================
# Typical pop lines:
# Jul 19 19:24:35 sb xinetd[1957]: START: pop3s pid=10806 from=70.181.95.115
# Jul 19 19:24:36 sb xinetd[1957]: EXIT: pop3s pid=10806 duration=1(sec)
proc got.pop3s {d h dp rest} {
global ipt
if {[lindex $rest 0] == "START:"} {
set exp1 {from=(\S+)}
if {[regexp $exp1 $rest -> ip]} {
if {[info exists ipt(ignore)]} {
foreach item $ipt(ignore) {
if {$item == $ip} {return}
}
}
log " pop $ip"
}
}
} ;# end of got.pop3s
#-----------------------------------------------------------------------
# Typical bad user line:
# Jul 19 19:22:09 sb sshd[10740]: Illegal user win from 213.193.225.13
proc got.Illegal {d h dp rest} {
global ipt
set exp1 {Illegal user (\S+) from (\S+)}
if 0 {
} elseif {[regexp $exp1 $rest -> user ip]} {
set ipt(iuf) "Illegal user from"
on.ip iuf $ip $user
} else {
error "got.Illegal($d : $rest)"
}
} ;# end of got.Illegal
#-----------------------------------------------------------------------
# Typical bad root attempt:
# Jul 19 19:17:18 sb sshd[10304]: Failed password for root from 60.48.155.19 port 60406 ssh2
# Jul 19 19:17:18 sb sshd[10304]: Failed password for illegal user junk from 60.48.155.19 port 60406 ssh2
proc got.Failed {d h dp rest} {
global ipt
set exp1 {Failed password for illegal user (\S+) from (\S+)}
set exp2 {Failed password for (\S+) from (\S+)}
set exp3 {^\D+([0-9\.]+)\s+.*}
if 0 {
} elseif {[regexp $exp1 $rest -> user ip]} {
set ipt(fpi) "Failed password from illegal user"
on.ip fpi $ip $user
} elseif {[regexp $exp2 $rest -> user ip]} {
set ipt(fpu) "Failed password from user"
on.ip [expr {$user == "root" ? "fpr" : "fpu"}] $ip $user
} elseif {[regexp $exp3 $rest -> ip]} {
error "1 got.Failed($d : $rest)"
set ipt(fpz) "Failed password regexp"
on.ip fpz $ip
} else {
error "2 got.Failed($d : $rest)"
}
} ;# end of got.Failed
#-----------------------------------------------------------------------
# Typical happening after the host has been droped:
# Jul 19 19:24:18 sb sshd[10750] fatal: Timeout before authentication for 213.193.225.13
proc got.fatal {d h dp rest} {
global ipt
set ip [lindex $rest end]
set exp1 {fatal: Timeout before authentication for (\S+)}
if 0 {
} elseif {[regexp $exp1 $rest -> user ip]} {
} else {
error "$got.fatal($d $rest)"
}
} ;# end of got.fatal
#-----------------------------------------------------------------------
# Typical 'Did not' message:
# Jul 19 18:28:56 sb sshd[8840]: Did not receive identification string from 213.193.225.13
proc got.Did {d h dp rest} {
global ipt
set ip [lindex $rest end]
set exp "Did not receive identification string "
if {![regexp $exp $rest]} {
error $rest
}
} ;# end of got.Did
#=======================================================================
proc iptables {act ip} {
global ipt
set table "INPUT"
switch -exact -- $act {
del { set opt -D }
default { set opt -I }
}
log " $act $ip"
if {[catch {exec iptables $opt $table -s $ip -j DROP} res]} {
log " exec of 'iptables $opt $table -s $ip' NOT started"
}
} ;# end of iptables
#-----------------------------------------------------------------------
proc accept {ip} {
global drop
if {[info exists drop($ip,st)] && [info exists drop($ip,id)]} {
after cancel $drop($ip,id)
# call iptables to issue an accept command for $ip.
iptables del $ip
}
} ;# end of accept
#-----------------------------------------------------------------------
proc drop {ip tp} {
global drop db cfg
if {![info exists drop($ip,st)]} {
data [format "drop: %15s %s:%s" $ip $tp $cfg($tp)]
set drop($ip,st) 1
# call iptables to issue a drop command for $ip, then:
iptables $tp $ip
if {![info exists cfg(delay)]} {set cfg(delay) 1h}
set drop($ip,id) [After $cfg(delay) accept $ip]
}
} ;# end of drop
#-----------------------------------------------------------------------
proc expire {index} {
global db
if {$db($index) > 1} { ;# yes leave it at one, not zero.
incr db($index) -1
}
} ;# end of expire
#-----------------------------------------------------------------------
proc on.ip {type ip {user {}}} {
global db cfg ipt
regsub {::ffff:} $ip "" ip
if {[info exists ipt(ignore)]} {
foreach item $ipt(ignore) {
if {$ip == $item} {return}
}
}
if {![info exists db($ip,$type)]} {
set db($ip,$type) 0
}
incr db($ip,$type)
if {[info exists cfg($type)] && $db($ip,$type) >= $cfg($type)} {
drop $ip $type
}
After 60s expire $ip,$type
} ;# end of on.ip
#=======================================================================
proc rdHandler {fh} {
global ipt
if {[eof $fh]} {
set ipt(end) "eof"
} elseif {[gets $fh line] != -1} {
# split the line into: date, host, daemon/port, theRest:
set exp1 {^\s*(\S+\s+\d+\s+\S+)\s+(\S+)\s+(\S+):\s+(.*)}
if {![regexp $exp1 $line -> d h dp rest]} {
error $line
} else {
switch -exact -- [lindex $rest 0] {
START: -
EXIT: { got.pop3s $d $h $dp $rest }
Illegal { got.Illegal $d $h $dp $rest }
Failed { got.Failed $d $h $dp $rest }
fatal: { got.fatal $d $h $dp $rest }
Did { got.Did $d $h $dp $rest }
default { ;# not yet handled:
error "$d | $h | $dp | $rest"
}
}
}
} else {
set ipt(end) -1
}
} ;# end of rdHandler
#-----------------------------------------------------------------------
proc show {} {
global db
set types [list iuf fpi fpu fpr]
for {set i 0} {$i < [llength $types]} {incr i} {
set n [lindex $types $i]
set $n $i
}
foreach i [array names db] {
set ip [lindex [split $i ,] 0]
set tmp($ip) [list 0 0 0 0]
}
foreach t $types {
foreach i [array names db *,$t] {
set it [split $i ,]
set ip [lindex $it 0]
set tp [lindex $it 1]
set tmp($ip) [lreplace $tmp($ip) [set $tp] [set $tp] $db($i)]
}
}
debug "\n IP Address iuf fpi fpu fpr"
foreach name [array names tmp] {
foreach {a b c d} $tmp($name) {break}
debug [format "%15s %4d %4d %4d %4d" $name $a $b $c $d]
}
debug ""
} ;# end of show
#-----------------------------------------------------------------------
proc ckFile {{how 0}} {
global ipt
file stat $ipt(file) stat
set inode $stat(ino)
if {![info exists ipt(inode)]} {set ipt(inode) ""}
if {$ipt(inode) != $inode} {set ipt(end) "new inode"}
set ipt(inode) $inode
After 60s ckFile 1
} ;# end of ckFile
#=======================================================================
proc tailFile {} {
global ipt
ckFile
while 1 {
if {[catch {set fh [open "|tail -f $ipt(file)" r]} out]} {
error "can NOT start '$out'"
}
set ipt(end) 0
fconfigure $fh -blocking 0 -buffering line
fileevent $fh readable [list rdHandler $fh]
vwait ipt(end)
log "Reopen $ipt(file) was $out"
catch {close $fh}
catch {exec kill -HUP $out}
After 1s catch {exec kill -HUP $out}
}
} ;# end of tailFile
#-----------------------------------------------------------------------
proc ckIgnore {} {
global cfg ipt
set ignore [set todo [list]]
if {[info exists cfg(ignore)]} {set todo $cfg(ignore)}
while {$todo != "" || [llength $todo] > 0} {
set doing [lindex $todo 0]
set todo [lrange $todo 1 end]
if {[string index $doing 0] == {\{}} {
;# if it starts with an open brace, assume regexp,
} elseif {[regexp {^\d+} $doing]} {
lappend ignore $doing ;# if its an ip, ignore that,
} elseif {[regexp {/} $doing]} {
catch { ;# filename if it contains a /
set fh [open $doing r]
set d1 [read $fh]
close $fh
foreach item [split $d1 \n] {
if {$item != ""} {lappend todo $item}
}
}
} else { ;# otherwise assume host name.
catch {
set fh [open "|dig +sh $doing" r]
set d1 [read $fh]
close $fh
}
foreach item [split $d1 \n] {
if {$item != ""} {lappend todo $item}
}
catch {exec ""}
}
}
set ipt(ignore) $ignore
set out [join $ignore ", "]
log " ignore $out"
After 1d ckIgnore
} ;# end of ckIgnore
#=======================================================================
ckIgnore
tailFile
#show
########################################################################
# End
