# Clone detection Version 3B from By-Tor & Ernst # 24/11/97 Ernst # Version 3B for eggdrop 1.3 ONLY <- you are HERE # Version 3A for eggdrop 1.1 and 1.2 ONLY # New in version 3B: # - Updated for eggdrop 1.3.x (Now also matches "channel flags") # - Fast kick of all clones at once (if IRC networks allows it) # - Console +1 also set for channel masters/ops # - Logging now goes to loglevel 1 of each channel (and not global level 1) # -- Installation/Usage ------------------------------------------------------- # - Copy it to your scripts/ directory # - Change/Check anything in the "Configuration" part bellow # - Install the script ('source scripts/clone_detect-3B.tcl' in bots config) # - Rehash or restart the bot. Wait one minute for initial clone-checking # - In bot's DCC/telnet, use following commands: # .console +1 - to be warned in bot when a clone joins/parts # .clones [#channel] - gives a list of clones in specified channel # .clonecheck [on/off] - turns clonechecking on or off # -- Configuration ------------------------------------------------------------ # "Fast-kick" - If your IRC server allows it, you can kick many people at once # in just one command (I have tried it with 20 kicks on IRCnet). If you think # this works on your irc network, set to "1", if not, set to "0". # Here is what I got by testing on some networks: # Dalnet - 0 # Efnet - 1 # IRCnet - 1 # Undernet - 1 set fastkick 1 # Is the whole clone-checking on or off by default? # (use .clonecheck on/off to change while bot is running) set clonecheck 1 # Hosts to except from clone-checking, wildcards (?*) allowed: # - hosts which more than one users can use at the same time # - hosts of your "friends" which are neither masters nor bots # - hosts of bots on the channel which have no +b flag # - etc etc # Note: Use HOST, the thing after the users "@" # e.g. "yo!yaba@yeba.edu" has host "yeba.edu" set noclonehost { "*.igt.net" "66.46.169.*" "*.undernet.org" } # People with those flags are never clones: (Separate the list with spaces) # Channel and Global flags are matched set nocloneflag "b c m" # How many minutes will a clone-host banning last set clonebantime 30 # If this number of clones is reached, bot kicks and bans set maxclone 3 # NOTICE other ops (if opped) when ANY clone joins/parts the channel? (0 = no) # (for paranoid people only!) set cloneopnotice 0 # -- Script ------------------------------------------------------------------- # A little proc to turn console +1 when a +m or +o joins: #bind chon - * dcc_on #proc dcc_on { hand idx } { # set chan [lindex [console $idx] 0] # if {[matchattr $hand m|m $chan] || [matchattr $hand o|o $chan]} { # console $idx +1 # } # return 0 #} # Opnotice proc, sends a notice to every op on a channel (if opped) proc opnotice { chan text } { global botnick if {![botisop $chan]} { return 0 } if {$text == ""} { return 0 } # I set maxnicks to send the same NOTICE to maximal 10 nicks at the same # time. Rise it if you want. (I don't know what the maximum IRC-allowed is) set maxnicks 10 set oplist "" set curnicks 0 foreach thisnick [chanlist $chan] { # don't notice who's not op, the bot itself and other bots if {([isop $thisnick $chan]) && ($thisnick != $botnick) && (![matchattr [nick2hand $thisnick $chan] b])} { incr curnicks # Add next op lappend oplist $thisnick # Reached maxnicks? if {$curnicks == $maxnicks} { # Send the text to the current list regsub -all " " $oplist "," oplist putbot Aide "notice $oplist :$text" # Clean up to begin next block set curnicks 0 set oplist "" } } } if {$oplist != ""} { # still some left to process regsub -all " " $oplist "," oplist putbot Aide "notice $oplist :$text" } } bind dcc GCOJ clones dcc_clone_stats bind dcc GC clonescheck dcc_clonecheck bind dcc GC clonecheck dcc_clonecheck # Kicks users matching $mask from $chan. Uses "fastkick", if enabled. # Returns a list of kicked users (may not have been executed, because of lag) proc kickall { mask chan reason } { global fastkick botnick if {![validchan $chan]} { return "" } set kicked "" foreach thisnick [chanlist $chan] { if {$thisnick == $botnick} { continue } if {[string match "$mask" "$thisnick![getchanhost $thisnick $chan]"]} { if {[botisop $chan]} { # We only can kick if we are op. But we want to return the list # of "kicked" even if we aren't if {$fastkick} { lappend knick $thisnick lappend kchan $chan } { putserv "KICK $chan $nick :$reason" } } lappend kicked $thisnick } if {$fastkick && [info exist knick]} { # Dump a KICK-line if it reaches 480 bytes if {[string length "KICK $knick $kchan :$reason"] > 480} { regsub -all " " $knick "," knick regsub -all " " $kchan "," kchan putserv "KICK $kchan $knick :$reason" unset knick unset kchan } } } if {$fastkick && [info exist knick]} { regsub -all " " $knick "," knick regsub -all " " $kchan "," kchan putserv "KICK $kchan $knick :$reason" } return "$kicked" } # Goes through the channel user list, marking clones. Called 10 secs after a # rehash/restart and 1 min after the bot joins the chan and after the bot is # opped (to kick eventual unkicked clones) proc set_clones { } { global clonelist botnick maxclone fastkick global noclonehost nocloneflag clonebantime if [array exists clonelist] { unset clonelist } foreach chan [channels] { set chan [string tolower $chan] set kicked "" foreach nick [chanlist $chan] { set hand [nick2hand $nick $chan] if {[lsearch -exact $kicked $nick] >= 0} { # This nick was already kicked continue } scan [string tolower [getchanhost $nick $chan]] "%\[^@]@%s" host host if [info exists clonelist($chan!$host)] { incr clonelist($chan!$host) 1 if {$clonelist($chan!$host) >= $maxclone} { newchanban $chan "*!*@$host" $botnick "Clones \($clonelist($chan!$host)\)" $clonebantime set thiskicked [kickall "*!*@$host" $chan "Clones provenant de $host"] set kicked [concat $kicked $thiskicked] putloglev 1 $chan "Avalanche de clones provenant de $host dans $chan: $thiskicked" } continue } set clonelist($chan!$host) 0 if {$nick == $botnick} {unset clonelist($chan!$host)} foreach exceptionflag $nocloneflag { if {[matchattr $hand $exceptionflag|$exceptionflag $chan]} { if {[info exists clonelist($chan!$host)]} { unset clonelist($chan!$host) } break } } if {[info exists clonelist($chan!$host)]} { foreach exceptionhost $noclonehost { # remove it, if it is an exception if {[string match [string tolower $exceptionhost] [string tolower $host]]} { unset clonelist($chan!$host) break } } } } } } # (i love those 6-level identations...) # Someone joining, check if this host already has someone here proc clone_detect { nick uhost hand chan } { global clonelist maxclone botnick global nocloneflag noclonehost clonebantime cloneopnotice derniercloneban set chan [string tolower $chan] # It is me joining! Let's rescan the channels list for clones in 1 min! if {$nick == $botnick} { # previous timer exists, kill it, start a new one foreach this [timers] { if {[string compare [lindex $this 1] "set_clones"] == 0} { killtimer [lindex $this 2] } } timer 1 set_clones return 0 } # Someone joining. Check if bot needs to update cloneslist first foreach this [timers] { if {[string compare [lindex $this 1] "set_clones"] == 0} { # Just relax, I need to refresh the clones-list first... return 0 } } foreach exceptionflag $nocloneflag { if {[matchattr $hand $exceptionflag|$exceptionflag $chan]} { return 0 } } scan [string tolower $uhost] "%\[^@]@%s" host host foreach exceptionhost $noclonehost { # if it is an exception do nothing if {[string match [string tolower $exceptionhost] $host]} {return 0} } if [info exists clonelist($chan!$host)] { incr clonelist($chan!$host) 1 if {$clonelist($chan!$host) >= $maxclone} { newchanban $chan "*!*@$host" $botnick "Clones \($clonelist($chan!$host)\)" $clonebantime set thiskicked [kickall "*!*@$host" $chan "Clones provenant de $host"] set derniercloneban "*!*@$host [unixtime]" putloglev 1 $chan "Avalanche de clones provenant de $host sur $chan: $thiskicked" return 0 } foreach nick2 [chanlist $chan] { scan [string tolower [getchanhost $nick2 $chan]] "%\[^@]@%s" host2 host2 if {$host == $host2 && $nick != $nick2} { lappend clonenicks $nick2 } } if [info exist clonenicks] { regsub -all " " $clonenicks ", " clonenicks if {$cloneopnotice} { opnotice $chan "Un Clone! $nick \($uhost\) est un clone de $clonenicks dans $chan" } putloglev 1 $chan "Un clone dans $chan: $nick \($uhost\) est clone de \#$clonelist($chan!$host) \($clonenicks\)" if [matchattr $hand p] { petitban [lindex $clonenicks 0] $chan "Clone ou fantome" putlog "Clone d'un membre: $nick ($hand) a amené un clone sur $chan. L'ancien nick a été kické." } } return 0 } set clonelist($chan!$host) 0 return 0 } proc clone_remove_part { nick uhost hand chan partmsg} { clone_remove $nick $uhost $hand $chan } proc clone_remove { nick uhost hand chan } { global clonelist botnick cloneopnotice nocloneflag noclonehost set chan [string tolower $chan] if {$nick == $botnick} {return 0} foreach this [timers] { if {[string compare [lindex $this 1] "set_clones"] == 0} { # Just relax, I need to refresh the clones-list first... return 0 } } scan [string tolower $uhost] "%\[^@]@%s" host host foreach exceptionflag $nocloneflag { if {[matchattr $hand $exceptionflag|$exceptionflag $chan]} { return 0 } } foreach exceptionhost $noclonehost { # if it is an exception do nothing if {[string match [string tolower $exceptionhost] [string tolower $host]]} {return 0} } if ![info exists clonelist($chan!$host)] {return 0} if {$clonelist($chan!$host) < 0} { putloglev 1 $chan "Heu, c'est étrange, j'ai un nombre négatif de Clones!!" return 0 } elseif {$clonelist($chan!$host) == 0} { unset clonelist($chan!$host) return 0 } elseif {$clonelist($chan!$host) > 0} { incr clonelist($chan!$host) -1 foreach n [chanlist $chan] { scan [string tolower [getchanhost $n $chan]] "%\[^@]@%s" host2 host2 if {($host == $host2) && ($n != $nick)} { lappend clonenicks $n } } if {[info exists clonelist($chan!$host)]&[info exist clonenicks]} { regsub -all " " $clonenicks ", " clonenicks putloglev 1 $chan "Clone SORTI $chan: $nick \($uhost\) a quitté \($clonenicks restant\)" if {$cloneopnotice} { opnotice $chan "Clone SORTI: $nick \($uhost\) a quitté $chan \($clonenicks restant\)" } } return 0 } } proc clone_kick { nick host hand chan knick rest } { clone_remove $knick [getchanhost $knick $chan] [nick2hand $knick $chan] $chan } proc clone_quit { nick host hand chan rest } { clone_remove $nick $host $hand $chan } proc dcc_clonecheck { hand idx onoff } { global clonecheck set choice [string tolower $onoff] if {$onoff == ""} { if {$clonecheck} { set status "on" } { set status "off" } putdcc $idx "État de la vérification des clones: \002${status}\002." return 0 } if {($choice != "on") && ($choice != "off")} { putdcc $idx "Usage: '.clonecheck \[on/off\]'" return 0 } if {$choice == "on"} { if {$clonecheck == 0} { set clonecheck 1 putdcc $idx "La détection des clones est maintenant à \002on\002." clonecheckload return 0 } { putdcc $idx "La détection des clones était déjà active." return 0 } } { if {$clonecheck == 1} { set clonecheck 0 putdcc $idx "La détection des clones est maintenant à \002off\002." clonecheckload return 0 } { putdcc $idx "La détection des clones était déjà désactivée." return 0 } } } proc dcc_clone_stats { hand idx chan } { global clonecheck if {$clonecheck == 0} { putdcc $idx "Désolé, la détection des clones est inactive en ce moment." return 0 } global clonelist if {$chan == ""} { set channel [lindex [console $idx] 0] } { regsub -nocase -all \[{}] $chan "" chan set channel [lindex $chan 0] } putdcc $idx "Clones dans $channel:" set channel [string tolower $channel] foreach addy [array names clonelist] { if {$clonelist($addy) > 0} { scan $addy "%\[^!]!%s" chan host if {$chan != [string tolower $channel]} { continue } foreach nick [chanlist $chan] { scan [string tolower [getchanhost $nick $chan]] "%\[^@]@%s" host2 host2 if {$host == $host2} { lappend clonenicks $nick } } } if [info exists clonenicks] { regsub -all " " $clonenicks ", " clonenicks set clones($host) $clonenicks unset clonenicks } } if [array exists clones] { set counter 0 foreach addy [array names clones] { incr counter putdcc $idx " $counter. $addy \($clones($addy)): $clonelist($channel!$addy)" } unset counter } { putdcc $idx "Aucun clone sur $channel" } return 1 } proc clonecheckload {} { global clonecheck clonelist clone_loaded if {$clonecheck} { # When activated, make all binds, and refresh clones-array bind join - * clone_detect bind part - * clone_remove_part bind sign - * clone_quit bind kick - * clone_kick bind splt - * clone_remove set clone_loaded 1 # begins in 10 secs (not ON rehash, cause chan-userlist is not ok yet) # if bot was restarted, set_clones is called 1 min after bot joins chan # (see clone_detect) utimer 10 set_clones } { # When not activated, remove all bindings and clean the clonelist from mem if {$clone_loaded} { unbind join - * clone_detect unbind part - * clone_remove_part unbind sign - * clone_quit unbind kick - * clone_kick unbind splt - * clone_remove } set clone_loaded 1 if {[array exists clonelist]} { unset clonelist } } } clonecheckload if {$clonecheck} { set clonestatus "on" } { set clonestatus "off" } unset clonestatus