#!/usr/local/bin/tclsh
#
# weed out certain undesirables from an eggdrop userlist
# try just typing 'tclsh weed' to find out the options
#    Robey Pointer, november 1994
#
# <cmwagner@gate.net>:
# I did a few bug fixes to the original weed script, things changed...
#
# when specifying other weed options they would unset the User() field and
# a maxlast weed would try and weed again and cause the script to stop due
# to User() being already unset  (array nonexistant)
#
# when loadUserFile encountered an xtra field it would try and use the $info
# variable, which was supposed to be $xtra (something overlooked when the
# line was cut and pasted -- I hate it when that happens)
#
# changed the formatting of the saved weed file to match more closely to
# eggdrop 0.9tp (so this may cause incompatibilities), but when a hostmask
# field exactly matched 40 characters it would save it with no spaces after
# it and eggdrop would reject the user record.  I know I could have easily
# changed one character, but I couldn't help myself.  <grin>
#                                         5 march 96
#
# <robey, 23jul96>:
#   upgrade for v2 userfiles
#
# <bruce s, 4sep96>:
#   fixed xtra field from getting truncated
#
# <robey, 20sep96>:
#   stopped it from mangling channel ban lists
#
# <Ec|ipse & dtM, 10jun97>:
#   upgrade for v3 userfiles
#
# <Ec|ipse 18jun97>:
#   added an option to remove users from unwanted channels


set exempt {*ban *ignore}
set exemptops 0 ; set exemptmasters 0 ; set exemptfriends 0
set exemptparty 0 ; set exemptfile 0 ; set exemptchanm 0
set exemptbotm 0 ; set exemptchann 0 ; set exemptchanf 0
set exemptchano 0
set maxlast 0 ; set maxban 0 ; set maxignore 0
set weedops 0 ; set weedmasters 0 ; set weednopw 0
set stripops 0 ; set stripmasters 0 ; set weedlurkers 0
set chanrem {}

set fd [open "/tmp/egg.timer." w]
close $fd
set CURRENT [file atime "/tmp/egg.timer."]
exec rm -f /tmp/egg.timer.

if {$argc < 1} {
	puts stdout "\nUsage: weed <userfile> \[options\]"
	puts stdout "  (weeds out users from an eggdrop userlist)"
	puts stdout "Output goes to <userfile>.weed"
	puts stdout "Possible options:"
	puts stdout "  -<nick>        exempt this user from weeding"
	puts stdout "  ^o  ^m  ^f     exempt ops or masters or friends"
	puts stdout "  ^co ^cm ^cf    exempt chanops or chanmasters or chanfriends"
	puts stdout "  ^cn            exempt chanowner"
	puts stdout "  ^p  ^x         exempt party-line or file-area users"
	puts stdout "  ^b             exempt botnet master"
	puts stdout "  +<days>        weed: haven't been seen in N days"
	puts stdout "  :n             weed: haven't EVER been seen"
	puts stdout "  :o  :m         weed: ops or masters with no password set"
	puts stdout "  :a             weed: anyone with no pasword set"
	puts stdout "  o   m          unop/unmaster: ops or masters with no pass."
	puts stdout "  b<days>        weed: bans not used in N days"
	puts stdout "  i<days>        weed: ignores created over N days ago"
	puts stdout "  =<chan>        weed: users on channels nolonger managed"
	puts stdout ""
	exit
}
puts stdout "\nWEED  18jun97, robey\n"

set filename [lindex $argv 0]
for {set i 1} {$i < $argc} {incr i} {
	set carg [lindex $argv $i]
	if {$carg == ":n"} { 
		set weedlurkers 1 
	} elseif {$carg == ":o"} {
		set weedops 1 ; set stripops 0 ; set weednopw 0
	} elseif {$carg == ":m"} { 
		set weedmasters 1 ; set stripmasters 0 ; set weednopw 0
	} elseif {$carg == ":a"} {
		set weednopw 1 ; set weedops 0 ; set weedmasters 0
		set stripops 0 ; set stripmasters 0
	} elseif {$carg == "o"} {
		set stripops 1 ; set weedops 0 ; set weednopw 0
	} elseif {$carg == "m"} {
		set stripmasters 1 ; set weedmasters 0 ; set weednopw 0
	} elseif {$carg == "^m"} {
		set exemptmasters 1
	} elseif {$carg == "^o"} {
		set exemptops 1
	} elseif {$carg == "^f"} {
		set exemptfriends 1
	} elseif {$carg == "^p"} {
		set exemptparty 1
	} elseif {$carg == "^x"} {
		set exemptfile 1
	} elseif {$carg == "^cf"} {
		set exemptchanf 1
	} elseif {$carg == "^cm"} {
		set exemptchanm 1
	} elseif {$carg == "^cn"} {
		set exemptchann 1
	} elseif {$carg == "^b"} {
		set exemptbotm 1
	} elseif {$carg == "^co"} {
		set exemptchano 1
	} elseif {[string index $carg 0] == "-"} {
		lappend exempt [string range $carg 1 end]
	} elseif {[string index $carg 0] == "+"} {
		set maxlast [expr 60*60*24* [string range $carg 1 end]]
	} elseif {[string index $carg 0] == "b"} {
		set maxban [expr 60*60*24* [string range $carg 1 end]]
	} elseif {[string index $carg 0] == "i"} {
		set maxignore [expr 60*60*24* [string range $carg 1 end]]
	} elseif {[string index $carg 0] == "="} {
		lappend chanrem [string tolower [string range $carg 1 end]]
	} else {
		puts stderr "UNKNOWN OPTION: '$carg'\n"
		exit
	}
}

if {(!$weedlurkers) && (!$weedops) && (!$weedmasters) && (!$weednopw) &&
		(!$stripops) && (!$stripmasters) && ($maxlast == 0) &&
		($maxban == 0) && ($maxignore == 0) && ($chanrem == {})} {
	puts stderr "PROBLEM: You didn't specify anything to weed out.\n"
	exit
}

set weeding { } ; set strip { } ; set exempting { }
if {$weedlurkers} { lappend weeding "lurkers" }
if {$weedops} { lappend weeding "pwdless-ops" }
if {$weedmasters} { lappend weeding "pwdless-masters" }
if {$weednopw} { lappend weeding "pwdless-users" }
if {$chanrem != {}} { lappend weeding "unwanted-channel" }
if {$maxlast > 0} { lappend weeding ">[expr $maxlast /(60*60*24)]-days" }
if {$maxban > 0} { lappend weeding "bans>[expr $maxban /(60*60*24)]-days" }
if {$maxignore > 0} { lappend weeding "ign>[expr $maxignore /(60*60*24)]-days" }
if {$weeding != { }} { puts stdout "Weeding:$weeding" }

if {$stripops} { lappend strip "pwdless-ops" }
if {$stripmasters} { lappend strip "pwdless-masters" }
if {$strip != { }} { puts stdout "Stripping:$strip" }

if {$exemptops} { lappend exempting "(ops)" }
if {$exemptmasters} { lappend exempting "(masters)" }
if {$exemptfriends} { lappend exempting "(friends)" }
if {$exemptparty} { lappend exempting "(party-line)" }
if {$exemptfile} { lappend exempting "(file-area)" }
if {$exemptchann} { lappend exempting "(channel-owners)" }
if {$exemptchanm} { lappend exempting "(channel-masters)" }
if {$exemptchano} { lappend exempting "(channel-ops)" }
if {$exemptchanf} { lappend exempting "(channel-friends)" }
if {$exemptbotm} { lappend exempting "(botnet masters)" }
if {[llength $exempt]>2} { lappend exempting "[lrange $exempt 2 end]" }
if {$exempting != { }} { puts stdout "Exempt:$exempting" }

puts stdout "\nReading $filename ..."

proc loadUserFile {fname} {
	global User Hostmask Channel LastOn
	set oldhandle {}
	if {[catch {set fd [open $fname r]}] != 0} { return 0 }
	set line [string trim [gets $fd]]
	if {[string range $line 1 2] != "3v"} {
		puts stderr "Unknown userfile version!  (not v3)\n"
		exit
	}
	while {![eof $fd]} {
		set line [string trim [gets $fd]]
		if {([string index $line 0] != "#") && ([string length $line] > 0)} {
			scan $line "%s" handle
			if {$handle == "-"} {
				# hostmask
				set hmList [split [string range $line 2 end] ,]
				for {set i 0} {$i < [llength $hmList]} {incr i} {
					lappend Hostmask($oldhandle) [string trim [lindex $hmList $i]]
				}
			} elseif {$handle == "!"} {
				# channel
				set chList [string trim [string range $line 1 end]]
				lappend Channel($oldhandle) [lrange $chList 0 end]
			} elseif {$handle == "*"} {
				# dccdir
				set dccdir [string trim [string range $line 2 end]]
				set User($oldhandle) [lreplace $User($oldhandle) 3 3 $dccdir]
			} elseif {$handle == "+"} {
				# email
				set email [string trim [string range $line 2 end]]
				set User($oldhandle) [lreplace $User($oldhandle) 4 4 $email]
			} elseif {$handle == "="} {
				# comment
				set comment [string trim [string range $line 2 end]]
				set User($oldhandle) [lreplace $User($oldhandle) 5 5 $comment]
			} elseif {$handle == ":"} {
				# user info line
				set info [string trim [string range $line 1 end]]
				set User($oldhandle) [lreplace $User($oldhandle) 6 6 $info]
			} elseif {$handle == "."} {
				# xtra field
				set xtra [lindex $User($oldhandle) 7]
				lappend xtra [string trim [string range $line 2 end]]
				set User($oldhandle) [lreplace $User($oldhandle) 7 7 $xtra]
			} elseif {$handle == "!!"} {
				# laston
				set LastOn($oldhandle) [lindex $line 1]
			} else {
				# begin of new user
				scan $line "%s %s %s %s" handle pass attr ts
				set User($handle) [list $pass $attr $ts {} {} {} {} {}]
				set Hostmask($handle) {}
				set Channel($handle) {}
				set oldhandle $handle
			}
		}
	}
	return 1
}

proc saveUserFile fname {
	global User Hostmask Channel LastOn
	if {[catch {set fd [open $fname w]}] != 0} { return 0 }
	puts $fd "#3v: weed!  now go away."
	foreach i [array names User] {
		set hmask "none"
		set hmloop 0 ; set chloop 0 ; set loloop 0
		set pass [lindex $User($i) 0]
		set attr [lindex $User($i) 1]
		set ts [lindex $User($i) 2]
		puts $fd [format "%-9s %-20s %-24s %s" $i $pass $attr $ts]
		for {} {$hmloop < [llength $Hostmask($i)]} {incr hmloop} {
			puts $fd "- [lindex $Hostmask($i) $hmloop]"
		}
		if {[info exists Channel($i)]} {
			for {} {$chloop < [llength $Channel($i)]} {incr chloop} {
				puts $fd "! [lindex $Channel($i) $chloop]"
			}
		}
		if {[lindex $User($i) 3] != {}} { puts $fd "* [lindex $User($i) 3]" }
		if {[lindex $User($i) 4] != {}} { puts $fd "+ [lindex $User($i) 4]" }
		if {[lindex $User($i) 5] != {}} { puts $fd "= [lindex $User($i) 5]" }
		if {[lindex $User($i) 6] != {}} { puts $fd ": [lindex $User($i) 6]" }
		if {[info exists LastOn($i)]} {
			puts $fd "!! [lindex $LastOn($i) 0]"
		}
		if {[lindex $User($i) 7] != {}} { 
			foreach xline [lindex $User($i) 7] {
				puts $fd ". $xline"
			}
		}
	}
	close $fd
	return 1
}

if {![loadUserFile $filename]} {
	puts stdout "* Couldn't load userfile!\n"
	exit
}
puts stdout "Loaded.  Weeding..."
puts stdout "(pwd = no pass, -o/-m = removed op/master, lrk = never seen, exp = expired)"
puts stdout "(uwc = unwanted channel)\n"

set total 0
set weeded 0
foreach i [array names User] {
	incr total
	set attr [lindex $User($i) 1]
	set chanattr [lindex [lindex $Channel($i) 0] 2]
	if {([lsearch -exact $exempt $i] == -1) &&	
		([string range $i 0 1] != "::") &&
		(([string first o $attr] == -1) || (!$exemptops)) &&
		(([string first m $attr] == -1) || (!$exemptmasters)) &&
		(([string first f $attr] == -1) || (!$exemptfriends)) &&
		(([string first p $attr] == -1) || (!$exemptparty)) &&
		(([string first x $attr] == -1) || (!$exemptfile)) &&
		(([string first B $attr] == -1) || (!$exemptbotm)) &&
		(([string first f $chanattr] == -1) || (!$exemptchanf)) &&
		(([string first m $chanattr] == -1) || (!$exemptchanm)) &&
		(([string first n $chanattr] == -1) || (!$exemptchann)) &&
		(([string first o $chanattr] == -1) || (!$exemptchano))} {
		set pass [lindex $User($i) 0]
		if {[info exists LastOn($i)]} {
			set ts $LastOn($i)
		} {
			set ts 0
		}
		if {([string compare $pass "-"] == 0) &&	([string first b $attr] == -1)} {
			if {$weednopw == 1} {
				unset User($i) ; incr weeded
				puts -nonewline stdout "[format "pwd: %-10s     " $i]"
			} elseif {([string first o $attr] != -1) && ($weedops == 1)} {
				unset User($i) ; incr weeded
				puts -nonewline stdout "[format "pwd: %-10s     " $i]"
			} elseif {([string first m $attr] != -1) && ($weedmasters == 1)} {
				unset User($i) ; incr weeded
				puts -nonewline stdout "[format "pwd: %-10s     " $i]"
			} 
			if {([string first o $attr] != -1) && ($stripops == 1)} {
				set nattr {}
				for {set x 0} {$x < [string length $attr]} {incr x} {
					if {[string index $attr $x] != "o"} {
						set nattr [format "%s%s" $nattr [string index $attr $x]]
					}
				}
				if {$nattr == {}} { set nattr "-" }
				set User($i) [lreplace $User($i) 1 1 $nattr]
				puts -nonewline stdout "[format " -o: %-10s     " $i]"
			}
			if {([string first m $attr] != -1) && ($stripmasters == 1)} {
				set nattr {}
				for {set x 0} {$x < [string length $attr]} {incr x} {
					if {[string index $attr $x] != "m"} {
						set nattr [format "%s%s" $nattr [string index $attr $x]]
					}
				}
				if {$nattr == {}} { set nattr "-" }
				set User($i) [lreplace $User($i) 1 1 $nattr]
				puts -nonewline stdout "[format " -m: %-10s     " $i]"
			}
		}
		if {($ts==0) && ($weedlurkers==1) && ([string first b $attr]==-1) && [info exists User($i)]} {
			unset User($i) ; incr weeded
			puts -nonewline stdout "[format "lrk: %-10s     " $i]"
		} 
		if {($ts > 0) && ($maxlast > 0) && ($CURRENT-$ts > $maxlast &&	[info exists User($i)])} {
			unset User($i) ; incr weeded
			puts -nonewline stdout "[format "exp: %-10s     " $i]"
		}
		if {$chanrem != {} && [info exists Channel($i)] && [info exists User($i)]} {
			if {[lsearch [string tolower $Channel($i)] *$chanrem*] != -1} {
				unset User($i) ; incr weeded
				puts -nonewline stdout "[format "uwc: %-10s     " $i]"
			}
		}
	}
	flush stdout
}
if {$weeded == 0} { puts -nonewline stdout "uNF... Nothing to weed dude!" }
puts stdout "\n"

foreach i [array names User] {
	if {([string range $i 0 1] == "::") || ($i == "*ban")} {
		for {set j 0} {$j < [llength $Hostmask($i)]} {incr j} {
			set ban [split [lindex $Hostmask($i) $j] :]
			if {[string range [lindex $ban 2] 0 0] == "+"} {
				set lastused [lindex $ban 3]
				if {($maxban > 0) && ($CURRENT-$lastused > $maxban)} {
					if {$i == "*ban"} {
						puts stdout "Expired ban: [lindex $ban 0]"
					} {
						puts stdout "Expired ban on [string range $i 2 end]: [lindex $ban 0]"
					}
					set Hostmask($i) [lreplace $Hostmask($i) $j $j]
					incr j -1
				}
			}
		}
	}
	if {$i == "*ignore"} {
		for {set j 0} {$j < [llength $Hostmask($i)]} {incr j} {
			set ign [split [lindex $Hostmask($i) $j] :]
			set lastused [lindex $ign 3]
			if {($maxignore > 0) && ($CURRENT-$lastused > $maxignore)} {
				puts stdout "Expired ignore: [lindex $ign 0]"
				set Hostmask($i) [lreplace $Hostmask($i) $j $j]
				incr j -1
			}
		}
	}
}

puts stdout "\nFinished scan."
puts stdout "Original total ($total), new total ([expr $total-$weeded]), zapped ($weeded)"

if {![saveUserFile $filename.weed]} {
	puts stdout "* Couldn't save new userfile!\n"
	exit
}
puts stdout "Wrote $filename.weed"
