####################################################
# by wiebe @ QuakeNet
#
#  bans clones
#  channel settings:
#  +clone (enable)
#  clone-max (max allowed clients from the same host)
#  clone-bantime (ban for X minutes)
#  clone-mode (how to react)
#  +clone-op (exclude opped users)
#  +clone-voice (exclude voiced users)
#  users with any of Cfvlomn flags for chan or global are excluded
#  clone-mode has 3 different modes:
#  mode 1 (ban all on *!*@host)
#  mode 2 (ban clients in excess of clone-max on *!user@host)
#  mode 3 (ban all on *!*@host when more than clone-max clones join within 180seconds)
#
####################################################

setudef flag clone
setudef flag clone-op
setudef flag clone-voice
setudef int clone-mode
setudef int clone-max
setudef int clone-bantime


####################################################
# clone:help:msgm
####################################################
bind msgm fvlomn|fvlomn "help clone" clone:help:msgm
proc clone:help:msgm { n u h t } {
  if {[matchattr $h bkZ]} { return 0 }
  lappend o "clone: usage clone add|del|list|set|info|help"
  if {[string equal [info procs cnotice] ""]} { foreach l $o { puthelp "NOTICE $n :$l" }
  } else { foreach l $o { cnotice $n $l puthelp "clone: " } }
  putcmdlog "($n!$u) !$h! help clone"
  return 1
}


####################################################
# clone:msg
####################################################
bind msg lomn|lomn clone clone:msg
proc clone:msg { n u h t } {
  if {[matchattr $h bkZ]} { return 0 }
  set t [split $t]; set c [string tolower [lindex $t 0]]; set t [join [lrange $t 1 end]]
  if {[string equal $c ""]} { lappend o "clone: usage clone add|del|list|set|info|help"
  } elseif {[string equal $c "add"]} { set o [clone:add $h $t]
  } elseif {[string equal $c "list"]} { set o [clone:list $h $t]
  } elseif {[string equal $c "del"]} { set o [clone:del $h $t]
  } elseif {[string equal $c "help"]} { set o [clone:help $t]
  } elseif {[string equal $c "set"]} { set o [clone:set2 $h $t]
  } elseif {[string equal $c "info"]} { set o [clone:info $h $t]
  } else {
    lappend o "clone: unknown command $c"
    lappend o "clone: usage clone add|del|list|set|info|help"
  }
  if {[string equal [info procs cnotice] ""]} { foreach l $o { puthelp "NOTICE $n :$l" }
  } else { foreach l $o { cnotice $n $l puthelp "clone: " } }
  return 1
}


####################################################
# clone:dcc
####################################################
bind dcc -|- clone clone:dcc
proc clone:dcc { h i t } {
  set t [split $t]; set c [string tolower [lindex $t 0]]; set t [join [lrange $t 1 end]]
  if {[string equal $c ""]} { lappend output "clone: usage clone add|del|list|set|info|help"
  } elseif {[string equal $c "add"]} { set output [clone:add $h $t]
  } elseif {[string equal $c "list"]} { set output [clone:list $h $t]
  } elseif {[string equal $c "del"]} { set output [clone:del $h $t]
  } elseif {[string equal $c "help"]} { set output [clone:help $t]
  } elseif {[string equal $c "set"]} { set output [clone:set2 $h $t]
  } elseif {[string equal $c "info"]} { set output [clone:info $h $t]
  } else {
    lappend output "clone: unknown command $c"
    lappend output "clone: usage clone add|del|list|set|info|help"
  }
  foreach l $output { putidx $i $l }
  return 1
}


####################################################
# clone:add
####################################################
# hand chan host duration comment
proc clone:add { h t } {
  set t [split $t]; set c [lindex $t 0]; set m [lindex $t 1]; set d [lindex $t 2]
  set r [join [lrange $t 3 end]]
  set n [join [split "\$CLONE $m *!*@*"] "\004"]
  if {![string equal $d ""]} { set d [clone:duration $d] }
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  if {[string equal -nocase $c "global"]} { set c [string tolower $c] }
  if {[string equal $c ""]} {
    lappend output "clone: no chan specified"
    lappend output "clone: add <chan> <host> <duration> <comment>"
    lappend output "clone: use \"global\" for chan to add global clone exemptions"
  } elseif {![validchan $c] && ![string equal $c "global"]} {
    lappend output "clone: no access or unknown channel $c"
    lappend output "clone: add <chan> <host> <duration> <comment>"
  } elseif {[string equal $c "global"] && ![matchattr $h omn]} {
    lappend output "clone: no access to add global clone exemptions"
  } elseif {![string equal $c "global"] && ![matchattr $h omn|omn $c]} {
    lappend output "clone: no access or unknown channel $c"
    lappend output "clone: add <chan> <host> <duration> <comment>"
  } elseif {[string equal $m ""]} {
    lappend output "clone: no host specified"
    lappend output "clone: add <chan> <host> <duration> <comment>"
    lappend output "clone: host is the host to ignore clones from and may contain wildcards"
  } elseif {[string equal $d ""] || $d < 0} {
    if {[string equal $d ""]} { lappend output "clone: no duration specified"
    } else { lappend output "clone: invalid duration" }
    lappend output "clone: add <chan> <host> <duration> <comment>"
    lappend output "clone: valid format is XyXmXwXdXhXnXs where y=year, m=month, w=week, d=day, h=hour, n=minute, s=second. Duration 0 is perm, minimal duration is 5 minutes."
  } elseif {[string equal $r ""]} {
    lappend output "clone: no comment specified"
    lappend output "clone: add <chan> <host> <duration> <comment>"
    lappend output "clone: comment is an internal note associated with the entry"
  } elseif {[string equal $c "global"]} {
    if {[string equal $d 0]} { set d2 perm } else { set d2 [clone:ts $d] }
    lappend output "CLONE: added global clone exempt host=$m duration=$d2 comment=$r"
    putloglev c * "CLONE: $h ADD $c $m $d2 $r"
    if {![string equal [info procs ircconsole] ""]} { ircconsole * S CLONE "$h ADD $m $d2 :$r" }
    set x ""; set y ""
    foreach x [channels] {
      if {[channel get $x dynamicexempts]} { continue }
      lappend y $x; channel set $x +dynamicexempts
    }
    newexempt $n $h $r $d
    foreach x $y { channel set $x -dynamicexempts }
  } else {
    if {[string equal $d 0]} { set d2 perm } else { set d2 [clone:ts $d] }
    lappend output "CLONE: added clone exempt host=$m chan=$c duration=$d2 comment=$r"
    putloglev c $c "CLONE: $h ADD $c $m $d2 $r"
    if {![string equal [info procs ircconsole] ""]} {
      ircconsole $c O CLONE "$h ADD $m $d2 :$r"
    }
    set e 0; if {![channel get $c dynamicexempts]} { set e 1 }
    if {$e} { channel set $c +dynamicexempts }
    newchanexempt $c $n $h $r $d
    if {$e} { channel set $c -dynamicexempts}
  }
  return $output
}


####################################################
# clone:list
####################################################
proc clone:list { h text } {
  set max 3; set text [split $text]; set range [lindex $text end]
  if {[string is digit $range]} { set text [lrange $text 0 end-1] } else { set range 1 }
  set c [lindex $text 0]; set m [lindex $text 1]; set r [join [lrange $text 2 end]]
  if {[string equal $r ""]} { set r * }
  set n "\$clone $m *!*@*"
  regsub -all {[][\\]} $m {\\\0} m
  regsub -all {[][\\]} $r {\\\0} r
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  if {[string equal -nocase $c "global"]} { set c [string tolower $c] }

  set g 0; if {[matchattr $h omn]} { set g 1 }
  if {[string equal $c "global"] && ![matchattr $h omn]} {
    foreach chan [channels] {
      if {![matchattr $h -|omn $chan]} { continue }; set g 1; break
    }
  }
# no chan
  if {[string equal $c ""]} {
    lappend output "clone: no chan specified"
    lappend output "clone: list <chan> <host> \[<comment>\] \[<range>\]"
    lappend output "clone: use \"global\" for chan to list global entries"
# invalid chan
  } elseif {![string equal $c "global"] && ![validchan $c]} {
    lappend output "clone: no access or unknown channel $c"
    lappend output "clone: list <chan> <host> \[<comment>\] \[<range>\]"
# no access global
  } elseif {[string equal $c "global"] && ![string equal $g "1"]} {
    lappend output "clone: no access to list global clone pattern"
    lappend output "clone: list <chan> <host> \[<comment>\] \[<range>\]"
# no access
  } elseif {![string equal $c "global"] && ![matchattr $h omn|omn $c]} {
    lappend output "clone: no access or unknown channel $c"
    lappend output "clone: list <chan> <host> \[<comment>\] \[<range>\]"
# nothing else
  } elseif {[string equal $m ""]} {
    lappend output "clone: list <chan> <host> \[<comment>\] \[<range>\]"
# list
  } else {
    if {[string equal $range "0"]} { set range2 ""
    } else {
      set range2 " - range [expr ($range -1) * $max +1]-[expr ($range -1) * $max + $max]"
    }
    if {[string equal $c "global"]} {
      set exempts [exemptlist]
      lappend output "clone: listing global entries$range2"
    } else {
      set exempts [exemptlist $c]
      lappend output "clone: listing entries for chan $c$range2"
    }
    set x 0; set y 0; set now [clock seconds]
    foreach exempt $exempts {
      set ho [lindex $exempt 0]; set ho [join [split $ho \004]]
# not a clone ban
      if {![string match -nocase "\$clone *" $ho]} { continue }; incr x 1
# no match
      if {![string match -nocase $m $ho]} { continue }
      incr y 1; set ho [split $ho]; set co [lindex $exempt 1]
# no match on comment
      if {![string match -nocase $r $co]} { continue }
# right range
      if {$y < [expr ($range -1) * $max +1]} { continue }
      if {$y > [expr ($range -1) * $max + $max]} { continue }
      set ex [clone:ts [expr ([lindex $exempt 2] - $now) / 60]]
      if {[string equal $ex "0"]} { set ex "expires never" } else { set ex "expires in $ex" }
      set ta "added [clone:ts [expr ($now - [lindex $exempt 3]) / 60]] ago"
      set cr [lindex $exempt 5]
      set m2 "host [join [lrange $ho 1 end-1]]"
      lappend output "clone: #$x   $m2   $ta   $ex   $cr: $co"
    }
    if {![string equal $max 0] && $y > $max && $y < $x} {
      lappend output "clone: use range [expr $range +1] to list next $max entries"
    }
    lappend output "clone: $y / $x entries match host=$m comment=$r"
  }
  return $output
}



####################################################
# clone:del
####################################################
proc clone:del { h text } {
  set text [split $text]; set c [lindex $text 0]
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  if {[string equal -nocase $c "global"]} { set c [string tolower $c] }
  set hash [lindex $text end]; set num [lrange $text 1 end]
  set md5i [string range [md5 "[string tolower $c] [lrange $text 1 end-1]"] 0 5]
  set md5o [string range [md5 "[string tolower $c] [lrange $text 1 end]"] 0 5]
  if {[string equal $hash $md5i]} { set num [lrange $text 1 end-1] }

  if {[string equal $c "global"]} { set exempts [exemptlist]
  } elseif {[validchan $c]} { set exempts [exemptlist $c] }
  if {[string equal $c "global"] || [validchan $c]} {
    set max [llength $exempts]; set err ""; set nums ""
    foreach n $num {
      if {![string is digit $n]} {
        set err "clone: $n is not a digit"; break
      } elseif {![string match -nocase "\$clone*" [lindex [lindex $exempts [expr $n -1]] 0]]} {
        set err "clone: $n is not a clone exempt"; break
      }
      if {[string equal [lsearch -exact $nums $n] "-1"]} { lappend nums $n }
    }
    set nums [join [lsort -decreasing -dictionary $nums]]
  }
# no chan
  if {[string equal $c ""]} {
    lappend output "clone: no chan specified"
    lappend output "clone: del <chan> <N1> \[<N2> <N3> ...\]"
    lappend output "clone: use \"global\" for chan to del global entries"
# invalid chan
  } elseif {![string equal $c "global"] && ![validchan $c]} {
    lappend output "clone: no access or unknown channel $c"
    lappend output "clone: del <chan> <N1> \[<N2> <N3> ...\]"
# no access global
  } elseif {[string equal $c "global"] && ![matchattr $h omn]} {
    lappend output "clone: no access to del global clone pattern"
    lappend output "clone: del <chan> <N1> \[<N2> <N3> ...\]"
# no access
  } elseif {![string equal $c "global"] && ![matchattr $h omn|omn $c]} {
    lappend output "clone: no access or unknown channel $c"
    lappend output "clone: del <chan> <N1> \[<N2> <N3> ...\]"
# nothing else
  } elseif {[string equal $num ""]} {
    lappend output "clone: del <chan> <N1> \[<N2> <N3> ...\]"
# error
  } elseif {![string equal $err ""]} {
    lappend output $err
# info 
  } elseif {![string equal $md5i $hash]} {
    lappend output "clone: to remove these patterns, use clone del [join $text] $md5o"
# delete
  } else {
    foreach n $nums {
      set exempt [lindex $exempts [expr $n -1]]; set m [lindex $exempt 0]
      set ho [lindex $exempt 0]; set ho [split $ho \004]; set ma [lindex $ho 1]
      set co "\"[lindex $exempt 1]\""; set et [lindex $exempt 2]; set ta [lindex $exempt 3]
      set cr [lindex $exempt 5]
      lappend output "clone: #$n   $ma   $co"

      if {[string equal $c "global"]} {
        putloglev c * "CLONE: $h DEL $c $ma :$co"
        killexempt $m
        if {![string equal [info procs ircconsole] ""]} {
          ircconsole * S CLONE "$h DEL $ma :$co"
        }
      } else {
        putloglev c $c "CLONE: $h DEL $c $ma :$co"
        killchanexempt $c $m
        if {![string equal [info procs ircconsole] ""]} { 
          ircconsole $c O CLONE "$h DEL $ma :$co"
        }
      }
    }
    if {[string equal $c "global"]} {
      lappend output "clone: deleted $nums global clone exempts"
    } else {
      lappend output "clone: deleted $nums clone exempts on $c"
    }
  }
  return $output
}



####################################################
# clone:set2
####################################################
proc clone:set2 { h t } {
  set t [split $t]; set c [lindex $t 0]; set t [lrange $t 1 end]
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  if {[string equal $c ""]} {
    lappend output "clone: no chan specified"
    lappend output "clone: set <chan> <setting> <value> \[<setting> <value> ...\]"
  } elseif {![validchan $c] || ![matchattr $h mn|mn $c]} {
    lappend output "clone: no access or unknown channel $c"
    lappend output "clone: set <chan> <setting> <value> \[<setting> <value> ...\]"
  } elseif {[string equal $t ""]} {
    lappend output "clone: no setting specified"
    lappend output "clone: set <chan> <setting> <value> \[<setting> <value> ...\]"
    lappend output "clone: settings are: clone max mode bantime op voice"
  } else {
    set p 0; set u [llength $t]; set o ""
    while {$p < $u} {
      set v [string tolower [lindex $t $p]]; set w [lindex $t [expr $p +1]]
      if {[string equal $v "clone"]} {
        if {[string equal -nocase $w "on"]} { channel set $c +clone
        } elseif {[string equal -nocase $w "off"]} { channel set $c -clone
        } else {
          lappend output "clone: no or invalid value for $v - value must be on or off"
          break
        }
      } elseif {![string equal [lsearch -exact "op voice" $v] "-1"]} {
        if {[string equal -nocase $w "on"]} { channel set $c +clone-$v
        } elseif {[string equal -nocase $w "off"]} { channel set $c -clone-$v
        } else {
          lappend output "clone: no or invalid value for $v - value must be on or off"
          break
        }
      } elseif {[string equal $v "max"]} {
        if {[string is digit $w] && $w > 1} { channel set $c clone-max $w
        } else {
          lappend output "clone: no or invalid value for $v - value must be a number greater than 1"
          break
        }
      } elseif {[string equal $v "mode"]} {
        if {[string match {[123]} $w]} { channel set $c clone-mode $w
        } else {
          lappend output "clone: no or invalid value for $v - value must be 1, 2 or 3"
          break
        }
      } elseif {[string equal $v "bantime"]} {
        if {[string is digit $w] && $w > 10} { channel set $c clone-bantime $w
        } else {
          lappend output "clone: no or invalid value for $v - value must be a number greater than 10"
          break
        }
      } else {
        if {![string equal $v ""]} { lappend output "clone: invalid setting $v" }
        break
      }
      incr p 2; lappend o "$v=$w"
    }
    if {![string equal $o ""]} { lappend output "clone: [join $o "     "]" }
  }
  return $output
}




####################################################
# clone:info
####################################################
proc clone:info { h t } {
  set c [lindex [split $t] 0]
  if {![validchan $c] && ![string equal [info procs whichchan] ""]} { set c [whichchan $h $c] }
  if {[string equal $c ""]} {
    lappend output "clone: no chan specified"
    lappend output "clone: info <chan>"
  } elseif {![validchan $c] || ![matchattr $h lomn|lomn $c]} {
    lappend output "clone: no access or unknown channel $c"
    lappend output "clone: info <chan>"
  } else {
    set x [channel get $c clone-max]
    if {$x < 2} { channel set $c clone-max 2; channel set $c -clone; set x 2 }
    if {[channel get $c clone]} { set s "yes" } else { set s "no" }
    set m [channel get $c clone-mode]
    if {![string match {[123]} $m]} { channel set $c clone-mode 3; set m 3 }
    set b [channel get $c clone-bantime]
    if {$b < 10} { channel set $c clone-bantime 60; set b 60 }
    set b [clone:ts $b]
    if {[channel get $c clone-op]} { set o "yes" } else { set o "no" }
    if {[channel get $c clone-voice]} { set v "yes" } else { set v "no" }
    lappend output "clone: clone active: $s     bantime: $b     exclude ops: $o     exclude voices: $v"
    if {[string equal $m "1"]} {
      lappend output "clone: mode 1: ban *!*@host when more than $x clients from same host are on the channel"
    } elseif {[string equal $m "2"]} {
      lappend output "clone: mode 2: ban every user exceeding $x on *!user@host"
    } else {
      lappend output "clone: mode 3: ban *!*@host when more than $x clients from the same host join within 180seconds"
    }    
  }
  return $output
}




####################################################
# clone:help
####################################################
proc clone:help { text } {
  set cmd [string tolower [lindex [split $text] 0]]
  if {[string equal $cmd "general"]} {
    lappend output "clone: bans clones, channel settings: \002+clone\002 (enable)   \002clone-max\002 (max allowed clients from the same host)   \002clone-bantime\002 (ban for X minutes)   \002clone-mode\002 (how to react)   \002+clone-op\002 (exclude opped users)   \002+clone-voice\002 (exclude voiced users), users with any of Cfvlomn flags for chan or global are excluded"
    lappend output "clone: clone-mode has 3 different modes: mode 1 (ban all on *!*@host), mode 2 (ban clients in excess of clone-max on *!user@host), mode 3 (ban all on *!*@host when more than clone-max clones join within 180seconds)"
  } elseif {[string equal $cmd "add"]} {
    lappend output "clone: add <chan> <host> <duration> <comment>"
    lappend output "clone: <host> is the host to ignore clones from and may contain wildcards, <duration> how long the entry should exist, <comment> is an internal note associated with the entry"
    lappend output "clone: use \"global\" for chan to add global entries"
  } elseif {[string equal $cmd "list"]} {
    lappend output "clone: list <chan> <host> \[<comment>\] \[<range>\]"
    lappend output "clone: list entries matching the given parameters, when the results exceed the maximum allowed output, use range to view the next set (default 1)"
    lappend output "clone: use \"global\" for chan to list global entries"
  } elseif {[string equal $cmd "del"]} {
    lappend output "clone: del <chan> <N1> \[<N2> <N3> ...\]"
    lappend output "clone: deletes the given clone exempt entry numbers obtained from clone list"
    lappend output "clone: use \"global\" for chan to del global entries"
  } elseif {[string equal $cmd "set"]} {
    lappend output "clone: set <chan> <setting> <value> \[<setting> <value> ...\]"
    lappend output "clone: changes channel settings"  
  } elseif {[string equal $cmd "info"]} {
    lappend output "clone: info <chan>"
    lappend output "clone: shows current channel settings"
  } else {
    lappend output "clone: help add|list|del|set|info|general"
    lappend output "clone: shows help in general or for the given subcommand"
  }
  return $output
}



####################################################
# clone:315
# RPL_ENDOFWHO <source> 315 <target> <mask> :End of /WHO list.
####################################################
bind raw - "315" clone:315
proc clone:315 { server num text } {
  set c [lindex [split $text] 1]; set c [clone:lower $c]
  if {![validchan $c]} { return 0 }
  if {![botonchan $c]} { return 0 }
  if {![channel get $c clone]} { return 0 }
  global clonedb
  if {[info exists clone(j,$c)]} { return 0 }
  clone:setall $c
  set clone(j,$c) 1
  return 0
}


####################################################
# clone:join
####################################################
bind join -|- * clone:join
proc clone:join { n u h c } {
  if {[isbotnick $n]} { return 0 }
  clone:set $n $u $c
}


####################################################
# clone:rejn
####################################################
bind rejn -|- * clone:rejn
proc clone:rejn { n u h c } { clone:set $n $u $c 1 }

####################################################
# clone:part  part sign splt
####################################################
bind part -|- * clone:part; bind sign -|- * clone:part; bind splt -|- * clone:part
proc clone:part { n u h c {m ""} } { clone:leave $n $u $c }

####################################################
# clone:kick
####################################################
bind kick -|- * clone:kick
proc clone:kick { n u h c t m } { clone:leave $t [getchanhost $t $c] $c }


####################################################
# clone:set
####################################################
proc clone:set { n u c {j 0} } {
  if {![validchan $c]} { return 0 }
  global clonedb
  set c [clone:lower $c]; set c2 $c
  if {[string length $c2] > 32} { set c2 [md5 $c2] }
  if {![channel get $c clone]} {
    if {![info exists clonedb(s,$c)] || ![string equal $clonedb(s,$c) "0"]} {
      clone:unsetall $c
      set clonedb(s,$c) 0
    }
    return 0
  } elseif {![info exists clonedb(s,$c)] || ![string equal $clonedb(s,$c) "1"]} {
    clone:setall $c
    return 0
  }
  set h [lindex [split $u @] 1]; set h2 [string tolower $h]; set h3 $h2
  if {[string length $h2] > 32} { set h2 [md5 $h2] }
  if {![info exists clonedb(c,$c2,$h2)]} { set clonedb(c,$c2,$h2) 0 }
  incr clonedb(c,$c2,$h2) 1
  set x [channel get $c clone-max]
  if {$x < "2"} { return 0 }
  set cc $clonedb(c,$c2,$h2)
  if {$cc <= $x} { return 0 }
  set y [channel get $c clone-mode]

  if {[matchban $n!$u $c]} { return 0 }
  if {[isexempt "\$CLONE\004$h\004*!*@*"]} { return 0 }
  if {[isexempt "\$CLONE\004$h\004*!*@*" $c]} { return 0 }
  if {[matchattr [nick2hand $n $c] Cbfvlomn|Cfvlomn $c]} { return 0 }

  if {[channel get $c clone-op] || [channel get $c clone-voice]} {
    regsub -all {[][\\]} $h3 {\\\0} h3
    foreach nc [chanlist $c] {
      if {![string match -nocase "*@$h3" [getchanhost $nc $c]]} { continue }
      if {[onchansplit $nc $c]} { continue }
      if {![isop $nc $c] && ![ishalfop $nc $c] && ![isvoice $nc $c]} { continue }
      if {[channel get $c clone-voice] && [isvoice $nc $c]} { return 0 }
      if {[channel get $c clone-op] && ([isop $nc $c] || [ishalfop $nc $c])} { return 0 }
    }
  }

# ban *!*@host
  if {[string equal $y "1"]} { set m *!*@$h; set r2 "$cc clients from $h"
# ban *!user@host
  } elseif {[string equal $y "2"]} { set m *!$u; set r2 "$cc clients from $h"
# mode 3 default
  } else {
# rejoin / setall - dont do mode 3
    if {$j} { return 0 }
    if {![string equal $y "3"]} { channel set $c clone-mode 3 }
    regsub -all {[][\\]} $h3 {\\\0} h3; set now [clock seconds]; set j "0"; set mode "3"
    foreach nc [chanlist $c] {
      set h4 [getchanhost $nc $c]
      if {![string match -nocase "*@$h3" $h4]} { continue }
      if {[matchban $nc!$h4 $c]} { continue }
      if {[onchansplit $nc $c]} { continue }
      set jt [getchanjoin $nc $c]
      if {[string equal $jt "0"]} { continue }
      if {[expr $now - $jt] > "180"} { continue }
      incr j 1
    }
    if {$j <= $x} { return 0 }
    set m *!*@$h; set r2 "$j clients from $h joined in less than 180s"
  }

  set r "Too many clients from your host."
  set l [channel get $c clone-bantime]
  if {$l < "10"} { set l 60; channel set $c clone-bantime $l }

  if {![string equal [info procs newchanban:ban] ""]} {
    if {![newchanban:ban $c $m clone.tcl $r $l]} { return 0 }
  } else { newchanban $c $m clone.tcl $r $l }

  set z [md5 "ban $m"]
  if {![string equal [info procs ircconsole] ""]} {
    ircconsole $c f CLONE "BAN $m ${l}m :$r2" $z
    putlog "CLONE BAN $m ${l}m :$r2"
  }
}


####################################################
# clone:setall
####################################################
proc clone:setall { c } {
  clone:unsetall $c
  if {![validchan $c]} { return 0 }
  if {![botonchan $c]} { return 0 }
  global clonedb
  set c [clone:lower $c]
  set nicks [chanlist $c]
  set clonedb(s,$c) 1
  while {![string equal [lindex $nicks end] ""]} {
    set n [lindex $nicks end]
    if {![onchansplit $n $c]} {
      set u [getchanhost $n $c]
      clone:set $n $u $c 1
    }
    set nicks [lrange $nicks 0 end-1]
  }
}


####################################################
# clone:leave
####################################################
proc clone:leave { n u c } {
  if {[isbotnick $n]} { clone:unsetall $c
  } elseif {[channel get $c clone]} { clone:unset $u $c }
}



####################################################
# clone:unset
####################################################
proc clone:unset { u c } {
  if {![validchan $c]} { return 0 }
  global clonedb
  set c [clone:lower $c]; set c2 $c
  if {[string length $c2] > 32} { set c2 [md5 $c2] }
  if {![channel get $c clone]} {
    if {![info exists clonedb(s,$c)] || ![string equal $clonedb(s,$c) "0"]} {
      clone:unsetall $c; set clonedb(s,$c) 0
    }
    return 0
  } elseif {![info exists clonedb(s,$c)] || ![string equal $clonedb(s,$c) "1"]} {
    clone:setall $c
  }
  set h [lindex [split $u @] 1]; set h [string tolower $h]; set h2 $h
  if {[string length $h2] > 32} { set h2 [md5 $h2] }
  if {![info exists clonedb(c,$c2,$h2)]} { return 0 }
  incr clonedb(c,$c2,$h2) -1
  if {[string equal $clonedb(c,$c2,$h2) "0"]} { unset clonedb(c,$c2,$h2) }
}


####################################################
# clone:unsetall
####################################################
proc clone:unsetall { c } {
  global clonedb
  set c [clone:lower $c]; set c2 $c
  if {[string length $c2] > 32} { set c2 [md5 $c2] }
  foreach n [array names clonedb] {
    set e [lindex [split $n ,] 1]
    if {[string equal $n s,$c] && [validchan $c]} { continue }
    if {![string equal $c2 $e]} { continue }
    unset clonedb($n)
  }
}


####################################################
# clone:evnt
####################################################
bind evnt -|- init-server clone:evnt; bind evnt -|- disconnect-server clone:evnt
proc clone:evnt { t } {
  global clonedb
  if {[info exists clonedb]} { unset clonedb }
  foreach c [channels] {
    set c [clone:lower $c]
    set clonedb(s,$c) [channel get $c clone]
  }
}


####################################################
# clone:lower
####################################################
proc clone:lower { t } {
  global rfc-compliant
  if {[info exists rfc-compliant] && [string equal ${rfc-compliant} "1"]} {
    set t [string map "\\{ \[ \\} \] ~ ^ \\\\ |" $t]
  }
  set t [string tolower $t]
  return $t
}


####################################################
# clone:duration
# transforms XyXMXwXdXhXmXs to minutes
####################################################
proc clone:duration { x } {
  set d 0; set e 0
  set x [string tolower $x]
  set x [string map "s SECOND n MINUTE h HOUR d DAY w WEEK m MONTH y YEAR" $x]
  if {[string equal $x 0]} { return $d }
  foreach y [split $x] {
    if {[catch {incr d [expr [clock scan $y] - [clock seconds]]} e]} { set e 1 }
  }
  if {$d < "300" && $d > "0"} { set d 5 } else { set d [expr $d / 60] }
  if {[string equal $d$e 01]} { set d -1 }
  return $d
}


####################################################
# clone:ts
# give minutes, returns XyXwXhXmXs
####################################################
proc clone:ts { t } {
  if {![string is digit $t]} { return 0 }
  set t [duration [expr $t * 60]]
  set t [string map "seconds s second s minutes m minute m hours h hour h" $t]
  set t [string map "days d day d weeks w week w years y year y" $t]
  return [join [lrange [split $t] 0 1]]
}


set scriptdb(clone) {
  "bans clones, channel settings: +clone (enable)   clone-max (max allowed clients from the same host)   clone-bantime (ban for X minutes)   clone-mode (how to react) +clone-op (exclude opped users)   +clone-voice (exclude voiced users), users with any of Cfvlomn flags for chan or global are excluded"
  "clone-mode has 3 different modes: mode 1 (ban all on *!*@host), mode 2 (ban clients in excess of clone-max on *!user@host), mode 3 (ban all on *!*@host when more than clone-max clones join within 180seconds)"
}

