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