#by wiebe @ QuakeNet
#requires whox.tcl
#requires dnsdb.tcl
#requires c_rule.tcl
#requires chase.tcl



set c_ban(defaultduration) "240"
set c_ban(maxdurationforops) "720"
set c_ban(defaultmask) "2"
set c_ban(xhost) "users.quakenet.org"



#help ban pub

bind pubm lomn|lomn "% ${botnet-nick} help ban" c_ban:help:pub

proc c_ban:help:pub { nick uhost handle chan text } {
  lappend output "Usage: ban <target> <reason>"
  lappend output "Usage: ban <target1> \[<target2> .. <targetN>\] \[%<XyXmXwXdXhXnXs>\] \[&<masktype>\] \[:\[@\]<reason>\]"
  lappend output "<target> can be a nick, a mask or #account which bans *!*@<account>.$::c_ban(xhost). When target is a nick, an attempt is made to ban the visible host, the *!*@<account>.$::c_ban(xhost) host (even when -x), and a dns-lookup is done to ban the resolved IP/hostname if needed."
  lappend output "Duration is specified with % parameter, where X are numbers, y=year m=month w=week d=day h=hour n=minute s=second (default [c_ban:ts $::c_ban(defaultduration)], lowest possible 5m). Only masters and owners can set bans longer than [c_ban:ts $::c_ban(maxdurationforops)] and permbans. Masktype is specified with the & parameter, &2 would ban the user by *!*@host (default $::c_ban(defaultmask))."
  lappend output "Parameter <reason> can be custom reason or one predefined from the rule command. The : prefixing the reason is required when using multiple targets, duration or type parameter. Prefix the reason itself with a @ to make it hidden, targets get kicked with reason 'You are banned'."
  if { [catch {set x [cnotice $nick $output]} error] || !$x } {
    foreach t $output { puthelp "NOTICE $nick :$t" }
  }
  putloglev c $chan "[lindex [split $text] 1]: $nick $uhost $handle $chan [join [lrange [split $text] 2 end]]"
}



#help banlist msg

bind msgm lomn|lomn "help ban" c_ban:help:msg

proc c_ban:help:msg { nick uhost handle text } {
  lappend output "Usage: ban <channel> <target> <reason>"
  lappend output "Usage: ban <channel> <target1> \[<target2> .. <targetN>\] \[%<XyXmXwXdXhXnXs>\] \[&<masktype>\] \[:\[@\]<reason>\]"
  lappend output "<target> can be a nick, a mask or #account which bans *!*@<account>.$::c_ban(xhost). When target is a nick, an attempt is made to ban the visible host, the *!*@<account>.$::c_ban(xhost) host (even when -x), and a dns-lookup is done to ban the resolved IP/hostname if needed."
  lappend output "Duration is specified with % parameter, where X are numbers, y=year m=month w=week d=day h=hour n=minute s=second (default [c_ban:ts $::c_ban(defaultduration)], lowest possible 5m). Only masters and owners can set bans longer than [c_ban:ts $::c_ban(maxdurationforops)] and permbans. Masktype is specified with the & parameter, &2 would ban the user by *!*@host (default $::c_ban(defaultmask))."
  lappend output "Parameter <reason> can be custom reason or one predefined from the rule command. The : prefixing the reason is required when using multiple targets, duration or type parameter. Prefix the reason itself with a @ to make it hidden, targets get kicked with reason 'You are banned'."
  if { [catch {set x [cnotice $nick $output]} error] || !$x } {
    foreach t $output { puthelp "NOTICE $nick :$t" }
  }
  putcmdlog "($nick!$uhost) !$handle! $text"
}



#ban pub
bind pubm lomn|lomn "% ${botnet-nick} ban" c_ban:pub
bind pubm lomn|lomn "% ${botnet-nick} ban *" c_ban:pub

proc c_ban:pub { nick uhost handle chan text } {
  set targets [join [lrange [split $text] 2 end]]
  if { [string equal $targets ""] } {
    lappend output "Usage: ban <target> <reason>"
    lappend output "Usage: ban <target1> \[<target2> .. <targetN>\] \[%<XyXmXwXdXnXs>\] \[&<masktype>\] \[:<reason>\]"
  } else {
    set output [c_ban:ban $handle $nick $chan $targets]
  }
  if { [catch {set x [cnotice $nick $output]} error] || !$x } {
    foreach t $output { puthelp "NOTICE $nick :$t" }
  }
  putloglev c $chan "[lindex [split $text] 1]: $nick $uhost $handle $chan [join [lrange [split $text] 2 end]]"
}



#ban msg
bind msg lomn|lomn ban c_ban:msg

proc c_ban:msg { nick uhost handle text } {
  set chan [lindex [split $text] 0]
  set targets [join [lrange [split $text] 1 end]]
  if { [string equal $chan ""] || [string equal $targets ""] } {
    lappend output "Usage: ban <channel> <target> <reason>"
    lappend output "Usage: ban <channel> <target1> \[<target2> .. <targetN>\] \[%<XyXmXwXdXnXs>\] \[&<masktype>\] \[:<reason>\]"
  } elseif { ![validchan $chan] || ![matchattr $handle lomn|lomn $chan] } {
    lappend output "No access or unknown channel $chan"
  } else {
    set output [c_ban:ban $handle $nick $chan $targets]
  }
  if { [catch {set x [cnotice $nick $output]} error] || !$x } {
    foreach t $output { puthelp "NOTICE $nick :$t" }
  }
  return 1
}



#ban
proc c_ban:ban { handle nick chan targets } {

#check reason
  set reason ""
  set p [lsearch -glob [split $targets] :*]
  if { ![string equal $p -1] } {
    set reason [string range [join [lrange [split $targets] $p end]] 1 end]
    set targets [join [lrange [split $targets] 0 [expr $p -1]]]
  } else {
    set reason [join [lrange [split $targets] 1 end]]
    set targets [lindex [split $targets] 0]
  }

#check duration
  set duration $::c_ban(defaultduration)
  set p [lsearch -glob [split $targets] \%*]
  if { ![string equal $p -1] } {
    set duration [string range [lindex [split $targets] $p] 1 end]
    set targets [join [lreplace [split $targets] $p $p]]
    set duration [c_ban:duration $duration]
    if { [string equal $duration -1] } {
      set duration $::c_ban(defaultduration)
      lappend output "Error converting duration parameter, using default duration ([c_ban:ts $duration])."
    }

#nick is not mn
    if { ![matchattr $handle mn|mn $chan] } {
      if { [string equal $duration 0] || $duration > $::c_ban(maxdurationforops) } {
        set duration $::c_ban(maxdurationforops)
        lappend output "Need to be a master or owner to use a duration that long, using max allowed duration ([c_ban:ts $duration])."
      }      
    } elseif { [string equal $duration 0] } {
      lappend output "Warning: you are setting perm bans."
    } elseif { $duration > "10080" } {
      lappend output "Warning: you are adding bans with excessive long duration."
    }
  }

#check masktype
  set masktype $::c_ban(defaultmask)
  set p [lsearch -glob [split $targets] \&*]
  if { ![string equal $p -1] } {
    set masktype [string range [lindex [split $targets] $p] 1 end]
    set targets [join [lreplace [split $targets] $p $p]]

#take care of leading 0
    if { [string is digit $masktype] && [string equal [string length $masktype] 2] } {
      set masktype [expr $masktype]
    }
    set p [lsearch -exact "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19" $masktype]
    if { [string equal $p -1] } { set masktype $::c_ban(defaultmask) }
  }

#didnt find reason, last target not on chan, assume last word is reason, adjust targets
  if { [string equal $reason ""] && ![onchan [lindex [split $targets] end] $chan] } {
    if { ![string equal [c_rule:rule [lindex [split $targets] end]] 0] } {
      set reason [lindex [split $targets] end]
      set targets [join [lrange [split $targets] 0 end-1]]
    }
  }

#set reason
  set temp 0
  set hid 0
  if { [string match @* $reason] } {
    set hid 1
    set reason [string range $reason 1 end]
  }
  if { [catch {set temp [c_rule:rule $reason]} error] } {
    putlog "ERROR using c_rule.tcl, perhaps you have not loaded it?"
  }
  if { ![string equal $temp 0] } { set reason $temp }

#use default
  if { [string equal $reason ""] } {
    if { [catch {set reason [c_rule:rule \#0]} error] } {
      putlog "ERROR using c_rule.tcl, perhaps you have not loaded it?"
      set reason "You are violating channel rules."
    }    
  }
  if { [string equal $hid 1] } { set reason @$reason }

#get all bans
  set bans ""
  foreach target [split $targets] {
    foreach ban [c_ban:getban $chan $target $masktype $nick $handle $duration $reason] {
      if { [string equal [lsearch $bans $ban] -1] } {
        lappend bans $ban
      }
    }
  }
  set widebans ""
  set abanned ""
  set pbanned ""
  set banned ""
  set mnbans ""
  foreach ban $bans {

    set rban $ban
    if { [string match =* $ban] } { set ban [string range $ban 1 end] }

#check if ban exists, added by +mn? nick not +mn? disallow overwrite!
    set creator ""
    if { [isban $ban $chan] } { set creator [c_ban:creator $chan $ban] }
    if { ![string equal $creator ""] && [matchattr $creator mn|mn $chan] && ![matchattr $handle mn|mn $chan] } {
      lappend mnbans $ban

#sanity check
    } elseif { [c_ban:sanity $chan $ban] } {
      lappend widebans $ban

#no = prefix used or -dynamicbans, activate ban
    } elseif { ![string match =* $rban] || ![channel get $chan dynamicbans]} {
      lappend abanned $ban
      if { [string equal [lsearch $banned $ban] -1] } { lappend banned $ban }

#passive ban
    } else {
      lappend pbanned $ban
      if { [string equal [lsearch $banned $ban] -1] } { lappend banned $ban }
    }
  }

#remove passive bans marked as active
  set pbanned2 ""
  foreach ban $pbanned {
    if { [string equal [lsearch $abanned $ban] -1] } { lappend pbanned2 $ban }
  }
  set pbanned $pbanned2

#get maxbans
  if { ![info exists ::max-bans] || ![string is digit ${::max-bans}] } {
    set maxbans 45
  } else { set maxbans ${::max-bans} }

#get modes-per-line
  if { ![info exists ::modes-per-line] || ![string is digit ${::modes-per-line}] } {
    set maxmodes 3
  } else { set maxmodes ${::modes-per-line} }

#set active bans
#bot (half)opped
#banlist not full
  if { ([botisop $chan] || [botishalfop $chan]) && [expr [llength [chanbans $chan]] +1] < $maxbans } {

    foreach ban $abanned {
      pushmode $chan +b $ban
      utimer [expr ((([llength $abanned] -1) / $maxmodes) * 2) +2] [list newchanban $chan $ban $handle $reason $duration]
    }

    if { ![string equal $abanned ""] } { flushmode $chan }
 } else { foreach ban $abanned { newchanban $chan $ban $handle $reason $duration } }

#set passive bans
 foreach ban $pbanned { newchanban $chan $ban $handle $reason $duration }

#output to user
  if { [string equal $banned ""] && [string equal $widebans ""] && [string equal $mnbans ""] } {
    lappend output "Found nothing to ban on $chan"
  } else {
    if { ![string equal $mnbans ""] } {
      lappend output "Can not overwrite bans set by masters or owners on $chan: [join $mnbans]"
    }
    if { ![string equal $widebans ""] } {
      lappend output "Mask too wide on $chan: [join $widebans]"
    }
    if { ![string equal $banned ""] } {
      if { [string equal $duration 0] } {
        set duration perm
      } else {
        set duration [c_ban:ts $duration]
      }
      lappend output "Banned on $chan: [join $banned] (duration: $duration) (reason: $reason)"
    }
  }
  return $output
}



#get the bans to set
proc c_ban:getban { chan target masktype nick handle duration reason } {
  if { [string equal $chan ""] } { return }
  if { [string equal $target ""] } { return }
  if { [string equal $masktype ""] } { return }
  set ban ""

#chasenick
  if { ![onchan $target $chan] } {
    if { [catch {set ct [chase:chasenick $target]} error] } {
      putlog "ERROR using chase.tcl, perhaps you have not loaded it?"
    }
    if { ![string equal $ct -1] && [onchan $ct $chan] && ![onchansplit $ct] } { set target $ct }
  }


#chase uhost
  set ct -1
  if { [catch {set ct [chase:chaseuhost $target $chan]} error] } {
    putlog "ERROR using chase.tcl, perhaps you have not loaded it?"
  }

  if { ![onchan $target $chan] && ![string equal $ct -1] } {
    set nuh $target!$ct

#check dnsdb.tcl
      set dnsdbip ""
      if { [catch {set dnsdbip [dnsdbchase:ip $target]} error] } {
        putlog "ERROR using dnsdb.tcl, perhaps you have not loaded it?"
      }
      set dnsdbhost ""
      if { [catch {set dnsdbhost [dnsdbchase:host $target]} error] } {
        putlog "ERROR using dnsdb.tcl, perhaps you have not loaded it?"
      }

#target has account host
    if { [string match -nocase *!*@?*.$::c_ban(xhost) $nuh] } {
      lappend ban *!*@[lindex [split $nuh @] 1]

#get account
    } else {
      set account 0
      if { [catch {set account [whox:chase $target a]} error] } {
        putlog "ERROR using whox.tcl, perhaps you have not loaded it?"
      }
      if { ![string equal $account 0] && ![string equal $account ""] } {
#target is registered but -x
        lappend ban *!*@$account.$::c_ban(xhost)
#ban the visible host
        lappend ban =[c_ban:mask $nuh $masktype]
      } else {
#ban the visible host
        lappend ban [c_ban:mask $nuh $masktype]
      }

#dnsdb.tcl failed, perform dns look-up
      if { [string equal $dnsdbhost -1] && [string equal $dnsdbip -1] } {
        set host [lindex [split $nuh @] 1]
        dnslookup $host c_ban:dnscallback $chan $nuh $masktype $nick $handle $duration $reason
      }
    }

#ban host and ip provided by dnsdb.tcl
    set nu [lindex [split $nuh @] 0]
    if { ![string equal $dnsdbhost -1] && ![string equal $dnsdbhost ""] } {
      lappend ban =[c_ban:mask $nu@$dnsdbhost $masktype]
    }
    if { ![string equal $dnsdbip -1] && ![string equal $dnsdbip ""] } {
      lappend ban =[c_ban:mask $nu@$dnsdbip $masktype]
    }

#nick
  } elseif { [onchan $target $chan] } {

    set nuh $target![getchanhost $target]

#check dnsdb.tcl
      set dnsdbip ""
      if { [catch {set dnsdbip [dnsdb:ip $target]} error] } {
        putlog "ERROR using dnsdb.tcl, perhaps you have not loaded it?"
      }
      set dnsdbhost ""
      if { [catch {set dnsdbhost [dnsdb:host $target]} error] } {
        putlog "ERROR using dnsdb.tcl, perhaps you have not loaded it?"
      }

#target has account host
    if { [string match -nocase *!*@?*.$::c_ban(xhost) $nuh] } {
      lappend ban *!*@[lindex [split $nuh @] 1]

#get account
    } else {
      set account 0
      if { [catch {set account [whox $target a]} error] } {
        putlog "ERROR using whox.tcl, perhaps you have not loaded it?"
      }
      if { ![string equal $account 0] && ![string equal $account ""] } {
#target is registered but -x
        lappend ban *!*@$account.$::c_ban(xhost)
      }
#ban the visible host
      lappend ban [c_ban:mask $nuh $masktype]

#dnsdb.tcl failed, perform dns look-up
      if { [string equal $dnsdbhost -1] && [string equal $dnsdbip -1] } {
        set host [lindex [split $nuh @] 1]
        dnslookup $host c_ban:dnscallback $chan $nuh $masktype $nick $handle $duration $reason
      }
    }

#ban host and ip provided by dnsdb.tcl
    set nu [lindex [split $nuh @] 0]
    if { ![string equal $dnsdbhost -1] && ![string equal $dnsdbhost ""] } {
      lappend ban =[c_ban:mask $nu@$dnsdbhost $masktype]
    }
    if { ![string equal $dnsdbip -1] && ![string equal $dnsdbip ""] } {
      lappend ban =[c_ban:mask $nu@$dnsdbip $masktype]
    }

#transform to correct masks
#user@host
  } elseif { [string match ?*@?* $target] && ![string match ?*!?*@?* $target] } {
    lappend ban *!$target

#host
  } elseif { [string match *.* $target] && ![string match ?*!?*@?* $target] } {
    lappend ban *!*@$target

#nick!user
  } elseif { [string match ?*!?* $target] && ![string match ?*!?*@?* $target] } {
    lappend ban $target@*

# #account
  } elseif { [string match \#?* $target] && ![string match ?*!?*@?* $target] } {
    lappend ban *!*@[string range $target 1 end].$::c_ban(xhost)

#nick
  } elseif { ![string match ?*!?*@?* $target] } {
    lappend ban $target!*@*
  } else {
    lappend ban $target
  }
  return $ban
}



#transforms XyXMXwXdXhXmXs to minutes
proc c_ban:duration { duration } {
  set d 0
  set e 0
  set duration [c_ban:tsr $duration]
  if { [string equal $duration 0] } { return $d }
  foreach part [split $duration] {
    if { [catch {incr d [expr [clock scan $part] - [unixtime]]} error] } { 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
}



#Xy Xm Xw Xd Xh Xn Xs returns Xyear Xmonth Xday Xhour Xminute Xseconds
proc c_ban:tsr { ts } {
  if { [string equal $ts ""] } { return }
  set ts [string tolower $ts]
  set ts [string map [list "s" "SECOND" "search" "replace" "search" "replace"] $ts]
  set ts [string map [list "m" "MONTH" "search" "replace" "search" "replace"] $ts]
  set ts [string map [list "n" "MINUTE" "search" "replace" "search" "replace"] $ts]
  set ts [string map [list "h" "HOUR" "search" "replace" "search" "replace"] $ts]
  set ts [string map [list "d" "DAY" "search" "replace" "search" "replace"] $ts]
  set ts [string map [list "w" "WEEK" "search" "replace" "search" "replace"] $ts]
  set ts [string map [list "y" "YEAR" "search" "replace" "search" "replace"] $ts]
  return $ts
}



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



proc c_ban:mask { nuhost type } {
#this proc is taken from:
# Clone Detector v3.0.2
#  by MC_8 - Carl M. Gregory <mc8 at purehype dot net>
#
# made a few changes. dont use maskhost it doesnt return the same stuff as mirc
# removed max host limit. mirc doesnt care about the length, so we dont here as well.
# changed [mask <nick|nick!user@host> <type>]
# changed ident to user(name)
# max username length is 10 chars
  if { [onchan $nuhost] } { set nuhost $nuhost![getchanhost $nuhost] }
  set type [expr {($type == "")?5:$type}]
  if {![regexp -- {^(1?[0-9])$} $type]} { return $nuhost }

  # Define the maximum length the ircd allows for an username.  Standard is 9,
  # however I made it to a variable incase someone wants to change it up.
  set user_max-length 10

  if { ![regexp -- {^(.*[^!])!((.*)@(.*))$} $nuhost -> nick uhost user host] } { return $nuhost }
  set maskhost 1
  if { [string equal [string length $type] 2] } {
    # Type must be 10-19.
    if { [info tclversion] < "8.1" } {
      set re_syntax_1 {([12][0-9][0-9]|[1-9][0-9]|[1-9])}
      set re_syntax_2 {([12][0-9][0-9]|[1-9][0-9]|[0-9])}
    } else {
      set re_syntax_1 {([12]\d{2}|[1-9][0-9]|[1-9])}
      set re_syntax_2 {([12]\d{2}|[1-9][0-9]|[0-9])}
    }
    set re_syntax ^$re_syntax_1\\.$re_syntax_2\\.$re_syntax_2\\.$re_syntax_2\$
    if { ![regexp -- $re_syntax $host] } {
      regsub -all -- {[0-9]} $host ? host
      set maskhost 0
    }
    set type [string index $type 1]
  }
  if { [string match {[0-4]} $type] } { set nick * }
  if { [string match {[2479]} $type] } { set user * }
  if { [string match {[1368]} $type] } { regsub -- {^~?(.*)$} $user *\\1 user }
  if { [string match {[3489]} $type] && $maskhost } {
    if { [regexp {^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$} $host] } {
      set host [join [lrange [split $host .] 0 2] .].*
    } elseif { [string match *.*.* $host] } { set host *.[join [lrange [split $host .] 1 end] .] }
  }
  if { [set length [string length $user]] > ${user_max-length} } {
    set user *[string range $user [expr $length-${user_max-length}] end]
  }
  return $nick!$user@$host
}



#sanity check, return 1 if mask is too wide
proc c_ban:sanity { chan mask } {

  if { ![validchan $chan] } { return 0 }
  if { [string equal $mask ""] } { return 0 }
  if { ![string match *!*@* $mask] } { return 0 }

  set nick [lindex [split $mask !] 0]
  set user [lindex [split $mask !] 1]
  set host [lindex [split $mask @] 1]
  set user [lindex [split $user @] 0]

#remove all ?'s and .'s and check if ONLY ?* / *.* pairs were used
  set mask2 [string map [list "?" "" "search" "replace" "search" "replace"] $mask]
  set mask2 [string map [list "." "" "search" "replace" "search" "replace"] $mask2]
  regsub -all {\*+} $mask {*} mask

#disallow any type of *!*@* ban that in effect matches everyone
  if { [string equal -nocase $mask2 *!*@*] } { return 1 }

#disallow banning any form of *!~*@* and *!^*@* (^ is same as ~ rfc1459)
  if { [string equal -nocase $mask2 *!~*@*] } { return 1 }
  if { [string equal -nocase $mask2 *!^*@*] } { return 1 }

#disallow banning any form of *!*@*.<xhost>, banning everyone authed/registered
  if { [string equal -nocase $mask2 *!*@*.$::c_ban(xhost)] } { return 1 }

#host is not an IP
  if { ![regexp {^(?:(?:2(?:[0-4]\d?|5[0-5])|[01]?\d{1,2})(?:\.|$)){4}$} $host] } {

#disallow banning any form of *!*@*.tld
    set mask2 [string map [list "?" "" "search" "replace" "search" "replace"] $mask]
    if { [string match -nocase {[*]![*]@[*].??} $mask2] } { return 1 }
    if { [string match -nocase {[*]![*]@[*].???} $mask2] } { return 1 }
    if { [string match -nocase {[*]![*]@[*].????} $mask2] } { return 1 }
  }

#check number of chars other than !@*?
  set nicklen $nick
  set nicklen [string map [list "*" "" "search" "replace" "search" "replace"] $nicklen]
  set nicklen [string map [list "?" "" "search" "replace" "search" "replace"] $nicklen]
  set nicklen [string length $nicklen]

  set userlen $user
  set userlen [string map [list "*" "" "search" "replace" "search" "replace"] $userlen]
  set userlen [string map [list "?" "" "search" "replace" "search" "replace"] $userlen]
  set userlen [string length $userlen]

  set hostlen $host
  set hostlen [string map [list "*" "" "search" "replace" "search" "replace"] $hostlen]
  set hostlen [string map [list "?" "" "search" "replace" "search" "replace"] $hostlen]
  set hostlen [string length $hostlen]

#host is not an IP
  if { ![regexp {^(?:(?:2(?:[0-4]\d?|5[0-5])|[01]?\d{1,2})(?:\.|$)){4}$} $host] } {

#shortest possible mask with host n!u@hhh.hh
   if { $hostlen < "5" && $userlen < "1" && $nicklen < "1" } { return 1 }

#shortest possible mask with IP n!u@i.i.i.i
  } else {
   if { $hostlen < "4" && $userlen < "1" && $nicklen < "1" } { return 1 }
  }

#cidr?
  if { [string match ?*.?*.?*.?*/?* $host] } {
    set cidr [lindex [split $host /] 1]
    set host [lindex [split $host /] 0]
    if { $cidr > "32" } { return 0 }

#found cidr and host is an IP
    if { [regexp {^(?:(?:2(?:[0-4]\d?|5[0-5])|[01]?\d{1,2})(?:\.|$)){4}$} $host] } {
      if { $cidr < "23" && $userlen < "1" && $nicklen < "1" } { return 2 }
    }
  }

#match mask to all users on the channel
  set count 0
  set match $mask
  regsub -all {\[} $match {\\[} match
  regsub -all {\]} $match {\\]} match
  regsub -all {\\} $match {\\\\} match
  if { [string match $match $::botname] } { return 1 }
  set nicks [chanlist $chan]
  set usercount [llength $nicks]
  foreach nick $nicks {
    if { [string match $match $nick![getchanhost $nick]] } { incr count 1 }
  }
  if { $count > "14" } { return 1 }
  set percentage [expr ($count / $usercount) * 100]
  set allowedpercentage [expr 10 + 300 / $usercount]
  if { $usercount > "3" && $percentage > $allowedpercentage } { return 1 }
  return 0
}



#dnscallback
proc c_ban:dnscallback { ip host status chan nuh masktype nick handle duration reason } {
  if { !$status } { return }
  if { ![validchan $chan] } { return }
  set oh [lindex [split $nuh @] 1]
  if { [string equal $oh $host] } { set nh $ip } else { set nh $host }
  set nuh [lindex [split $nuh @] 0]@$nh
  set ban [c_ban:mask $nuh $masktype]
  newchanban $chan $ban $handle $reason $duration
  if { [string equal $duration 0] } {
    set duration perm
  } else {
    set duration [c_ban:ts $duration]
  }
  lappend output "Banned (dns lookup): $ban (duration: $duration) (reason: $reason)"
  if { [catch {set x [cnotice $nick $output]} error] || !$x } {
    foreach t $output { puthelp "NOTICE $nick :$t" }
  }
}



#creator
proc c_ban:creator { chan banmask } {
  if { ![validchan $chan] } { return }
  if { [string equal $banmask ""] } { return }
  foreach ban [banlist $chan] {
    set hostmask [lindex $ban 0]
    set creator [lindex $ban 5]
    if { [string equal -nocase $hostmask $banmask] } {
      if { [validuser $creator] } { return $creator } else { return }
    }
  }
  return
}

