# shroudBNC - an object-oriented framework for IRC
# Copyright (C) 2005 Gunnar Beutner
#
# Modified By Worrum
#
# * added password restriction
# * added /sbnc vhostwho command, to give userstatistic per vhost
# * fixed vhosts with limit 0 (private) being visible to admins
# * fixed vhosts/vhost selecting looking at the _actual_ connection
#   which are in use, and not just given with /sbnc set (and thus not actually used yet)
# * fixed proper _free_ vhost selection on usercreate (it doesnt pick 0 limit, or passworded
#   anymore) if all vhosts are full, vhost '0' is set, disallowing the user to connect.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

internalbind command vhost:command
internalbind usrcreate vhost:newuser

# Give regular user access to any vhost being not 0 in limit password is default set to root (changable through /sbnc vhostoverride)
if {[bncgetglobaltag vhost.override] == ""} {
	bncsetglobaltag vhost.override "root"
}


proc vhost:host2ip {host} {
	set vhosts [bncgetglobaltag vhosts]

	if {[lsearch -exact [info procs] "vhost_hack:getadditionalvhosts"] != -1} {
		set vhosts [concat $vhosts [vhost_hack:getadditionalvhosts]]
	}

	foreach vhost $vhosts {
		if {[string equal -nocase [lindex $vhost 2] $host]} {
			return [lindex $vhost 0]
		}
	}

	return $host
}

proc vhost:ip2host {ip} {
	set vhosts [bncgetglobaltag vhosts]
	set ip [vhost:expandipv6 $ip]
	if {[lsearch -exact [info procs] "vhost_hack:getadditionalvhosts"] != -1} {
		set vhosts [concat $vhosts [vhost_hack:getadditionalvhosts]]
	}

	foreach vhost $vhosts {
		if {[string equal -nocase [lindex $vhost 0] $ip]} {
			return [lindex $vhost 2]
		}
	}

	return $host
}

proc vhost:countvhost {ip} {
	set count 0

	set ip [vhost:host2ip $ip]

	foreach user [bncuserlist] {
		if {![getbncuser $user hasserver]} {
			if {[string equal -nocase [getbncuser $user vhost] $ip]} {
				incr count
			}
		} else  {
			if {[string equal -nocase [vhost:expandipv6 [getbncuser $user localip]] $ip]} {
				incr count
			}
		}
	}

	return $count
}

proc vhost:vhostusers {ip} {
	set count ""
	set listcount ""
	set ip [vhost:host2ip $ip]

	foreach user [lsort [bncuserlist]] {
		if {![getbncuser $user hasserver]} {
			if {[string equal -nocase [getbncuser $user vhost] $ip]} {
				lappend count $user
			}
		} else  {
			if {[string equal -nocase [vhost:expandipv6 [getbncuser $user localip]] $ip]} {
				lappend count $user
			}
		}
		if {([llength $count] == 10)} {
			lappend listcount $count
			set count ""
		}
	}
	if {([llength $count] > 0)} {
		lappend listcount $count
	}
	if {$listcount == ""} { set listcount "None"	}
	return $listcount
}

proc vhost:expandipv6 {ip} {
	if {[string match *:* $ip] == 0} { return $ip }
	set newip ""
	set ip [string map {{::} {:ZZZZ:}} $ip]
	foreach x [split $ip {:}] {
		lappend newip [string range "0000$x" end-3 end]
	}
	set newip [join $newip {:}]
	set i 0
	while {[llength [split $newip {:}]] < 8} {
		incr i
		if {$i > 10} { return 0 }
		set newip [string map {{:ZZZZ:} {:0000:ZZZZ:}} $newip]
	}
	return [string map {{Z} {0}} $newip]
}

proc vhost:getlimit {ip} {
	set vhosts [bncgetglobaltag vhosts]
	set ip [vhost:host2ip $ip]

	if {[lsearch -exact [info procs] "vhost_hack:getadditionalvhosts"] != -1} {
		set vhosts [concat $vhosts [vhost_hack:getadditionalvhosts]]
	}

	set res [lsearch -inline $vhosts "$ip *"]

	if {$res != ""} {
		return [lindex $res 1]
	} else {
		return -1
	}
}

proc vhost:getpassword {ip} {
	set vhosts [bncgetglobaltag vhosts]
	set ip [vhost:host2ip $ip]

	if {[lsearch -exact [info procs] "vhost_hack:getadditionalvhosts"] != -1} {
		set vhosts [concat $vhosts [vhost_hack:getadditionalvhosts]]
	}

	set res [lsearch -inline $vhosts "$ip *"]

	if {$res != ""} {
		return [lindex $res 3]
	} else {
		return 0
	}
}


proc vhost:isip {host} {
	return [regexp -nocase -- {^(^(([0-9A-F]{1,4}(((:[0-9A-F]{1,4}){5}::[0-9A-F]{1,4})|((:[0-9A-F]{1,4}){4}::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,1})|((:[0-9A-F]{1,4}){3}::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,2})|((:[0-9A-F]{1,4}){2}::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,3})|(:[0-9A-F]{1,4}::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,4})|(::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,5})|(:[0-9A-F]{1,4}){7}))$|^(::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,6})$)|^::$)|^((([0-9A-F]{1,4}(((:[0-9A-F]{1,4}){3}::([0-9A-F]{1,4}){1})|((:[0-9A-F]{1,4}){2}::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,1})|((:[0-9A-F]{1,4}){1}::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,2})|(::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,3})|((:[0-9A-F]{1,4}){0,5})))|([:]{2}[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,4})):|(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{0,2})\.){3}(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{0,2})$$} $host]
}

proc vhost:command {client parameters} {
	if {![getbncuser $client admin] && [string equal -nocase [lindex $parameters 0] "set"] && [string equal -nocase [lindex $parameters 1] "vhost"]} {
		if {[lsearch -exact [info commands] "lock:islocked"] != -1} {
			if {![string equal [lock:islocked [getctx] "vhost"] "0"]} { return }
		}
		if {[lrange $parameters 2 end] == ""} {
			bncreply "Syntax: set vhost <ip> \[<password>\]"
			haltoutput
			return
		}
		if {![vhost:isip [lindex $parameters 2]]} {
			bncreply "You have to specify a valid IP address."

			haltoutput
			return
		}
		set limit [vhost:getlimit [lindex $parameters 2]]

		if {$limit <= 0 } {
			bncreply "Sorry, you may not use this IP address/hostname."

			haltoutput
			return
		} elseif {[vhost:countvhost [lindex $parameters 2]] >= $limit} {
			bncreply "Sorry, the IP address [lindex $parameters 2] is already being used by [vhost:countvhost [lindex $parameters 2]] users. A maximum number of [vhost:getlimit [lindex $parameters 2]] users may use this IP address."

			haltoutput
			return
		}
		if {!([vhost:getpassword [lindex $parameters 2]] == "")} {
			if {([lindex $parameters 3] == "")} {
				haltoutput
				bncreply "This vhost requires a password, use set vhost <ip> <password>"
				return
			}
			if {!([vhost:getpassword [lindex $parameters 2]] == [lindex $parameters 3]) && !([bncgetglobaltag vhost.override] == [lindex $parameters 3])} {
				haltoutput
				bncreply "Sorry, the entered password is incorrect"
				return
			}
		}
	}

	set vhosts [bncgetglobaltag vhosts]

	if {[lsearch -exact [info procs] "vhost_hack:getadditionalvhosts"] != -1} {
		set vhosts [concat $vhosts [vhost_hack:getadditionalvhosts]]
	}

	if {[string equal -nocase [lindex $parameters 0] "vhosts"]} {
		foreach vhost $vhosts {
			if {[getbncuser $client admin]} {
				if {!([vhost:getpassword [lindex $vhost 0]] == "")} {
					bncreply "[lindex $vhost 0] ([lindex $vhost 2]) [vhost:countvhost [lindex $vhost 0]]/[vhost:getlimit [lindex $vhost 0]] connections \[password required: [vhost:getpassword [lindex $vhost 0]]\]"
				} else  {
					bncreply "[lindex $vhost 0] ([lindex $vhost 2]) [vhost:countvhost [lindex $vhost 0]]/[vhost:getlimit [lindex $vhost 0]] connections"
				}
			} elseif {[vhost:getlimit [lindex $vhost 0]] > 0} {
				if {[vhost:countvhost [lindex $vhost 0]] >= [vhost:getlimit [lindex $vhost 0]]} {
					set status "full"
				} else {
					set status "not full"
				}
				if {!([vhost:getpassword [lindex $vhost 0]] == "")} {
					lappend status "\[passworded\]"
				}
				bncreply "[lindex $vhost 0] ([lindex $vhost 2]) \[[join $status]\]"
			}
		}

		bncreply "-- End of VHOSTS."

		haltoutput
	}
	if {[string equal -nocase [lindex $parameters 0] "vhostwho"] && [getbncuser $client admin]} {
		if {[lindex $parameters 1] == ""} {
			foreach vhost $vhosts {
				if {[vhost:getlimit [lindex $vhost 0]] > -1} {
					bncreply "[lindex $vhost 0] ([lindex $vhost 2]) usage: ([vhost:countvhost [lindex $vhost 0]]/[vhost:getlimit [lindex $vhost 0]])"
					set userlist [vhost:vhostusers [lindex $vhost 0]]
					foreach group $userlist { bncreply "users: $group" }
				}
			}
			bncreply "-- End of VHOSTS."
			haltoutput
			return
		} else {
			set vhost [lindex $parameters 1]
			if {[vhost:getlimit $vhost] == "-1"} { bncreply "No such vhost." ; haltoutput ; return }
			set vhost [lsearch -inline $vhosts "$vhost *"]
			bncreply "[lindex $vhost 0] ([lindex $vhost 2]) usage: ([vhost:countvhost [lindex $vhost 0]]/[vhost:getlimit [lindex $vhost 0]])"
			set userlist [vhost:vhostusers [lindex $vhost 0]]
			foreach group $userlist { bncreply "users: $group" }
			haltoutput
		}
	}
	if {[getbncuser [getctx] admin] && [string equal -nocase [lindex $parameters 0] "addvhost"]} {
		set ip [lindex $parameters 1]
		set limit [lindex $parameters 2]
		set host [lindex $parameters 3]
		set password [lindex $parameters 4]

		if {$host == ""} {
			bncreply "Syntax: ADDVHOST <ip> <limit> <host> \[password\]"

			haltoutput
			return
		}

		if {![vhost:isip $ip]} {
			bncreply "You did not specify a valid IP address."

			haltoutput
			return
		}

		if {![string is integer $limit]} {
			bncreply "You did not specify a valid limit."

			haltoutput
			return
		}

		if {[catch [list vhost:addvhost $ip $limit $host $password] error]} {
			bncreply $error
		} else {
			bncreply "Done."
		}

		haltoutput
	}

	if {[getbncuser [getctx] admin] && [string equal -nocase [lindex $parameters 0] "changevhost"]} {
		set ip [lindex $parameters 1]
		set limit [lindex $parameters 2]
		set host [lindex $parameters 3]
		set password [lindex $parameters 4]

		if {$host == ""} {
			bncreply "Syntax: CHANGEVHOST <ip> <limit> <host> \[password\]"

			haltoutput
			return
		}

		if {![vhost:isip $ip]} {
			bncreply "You did not specify a valid IP address."

			haltoutput
			return
		}

		if {![string is integer $limit]} {
			bncreply "You did not specify a valid limit."

			haltoutput
			return
		}
		if {[catch [list vhost:delvhost $ip] error]} {
			bncreply $error
			haltoutput
			return
		}
		if {[catch [list vhost:addvhost $ip $limit $host $password] error]} {
			bncreply $error
		} else {
			bncreply "Done."
		}
		haltoutput
	}

	if {[getbncuser [getctx] admin] && [string equal -nocase [lindex $parameters 0] "delvhost"]} {
		set ip [vhost:host2ip [lindex $parameters 1]]

		if {$ip == ""} {
			bncreply "Syntax: DELVHOST <ip>"

			haltoutput
			return
		}

		if {[catch [list vhost:delvhost $ip] error]} {
			bncreply $error
		} else {
			bncreply "Done."
		}

		haltoutput
	}

	if {[getbncuser [getctx] admin] && [string equal -nocase [lindex $parameters 0] "vhostoverride"]} {
		if {[lindex $parameters 1] == ""} {
			bncreply "Syntax: VHOSTOVERRIDE <password>"
			bncreply "The current password is: [bncgetglobaltag vhost.override]"

			haltoutput
			return
		} else  {
			bncsetglobaltag vhost.override [lindex $parameters 1]
			bncreply "Done."
		}
		haltoutput
	}

	if {[string equal -nocase [lindex $parameters 0] "help"]} {
		if {[getbncuser [getctx] admin]} {
			bncaddcommand addvhost Vhost "adds a new vhost" "Syntax: addvhost ip limit host \[password\]\nAdds a new vhost."
			bncaddcommand delvhost Vhost "removes a vhost" "Syntax: delvhost ip\nRemoves a vhost."
			bncaddcommand vhostoverride Vhost "sets the vhost override password" "Syntax: vhostoverride password \nSets the vhost override password which allows regular users to use any passworded vhost, using this password."
			bncaddcommand vhostwho Vhost "list vhostusage" "Syntax: vhostwho \[vhost\]\n view the list, or select just a single ip/vhost"
			bncaddcommand changevhost Vhost "ability to change an existing vhost" "Syntax: changevhost ip limit host \[password\]\nChanges an existing vhost."
			bncaddcommand vhosts Vhost "lists all available vhosts" "Syntax: vhosts\nDisplays a list of all available virtual vhosts."
		} else {
			bncaddcommand vhosts User "lists all available vhosts" "Syntax: vhosts\nDisplays a list of all available virtual vhosts."
		}
	}
}

proc vhost:findip {} {
	set vhosts [bncgetglobaltag vhosts]

	if {[lsearch -exact [info procs] "vhost_hack:getadditionalvhosts"] != -1} {
		set vhosts [concat $vhosts [vhost_hack:getadditionalvhosts]]
	}
	set min 0
	set minip 0

	foreach vhost $vhosts {
		if {([lindex $vhost 1] - [vhost:countvhost [lindex $vhost 0]] > $min) && ([vhost:getpassword [lindex $vhost 0]] == "")} {
			set min [expr [lindex $vhost 1] - [vhost:countvhost [lindex $vhost 0]]]
			set minip [lindex $vhost 0]
		}
	}
	return $minip
}

proc vhost:newuser {user} {
	setbncuser $user vhost [vhost:findip]
}

proc vhost:addvhost {ip limit host {password ""}} {
	if {[vhost:getlimit $ip] != -1} {
		return -code error "This vhost has already been added."
	} else {
		if {[string length $limit] == 0 || ![string is integer $limit] || ($limit <0)} {
			return -code error "You need to specify a valid limit."
		}

		if {[string length $ip] == 0} {
			return -code error "You need to specify a valid IP address."
		}

		if {[string length $host] == 0} {
			return -code error "You need to specify a valid hostname."
		}

		set vhosts [bncgetglobaltag vhosts]
		lappend vhosts [list $ip $limit $host $password]
		bncsetglobaltag vhosts $vhosts
	}
}

proc vhost:delvhost {ip} {
	set vhosts [bncgetglobaltag vhosts]
	set ip [vhost:host2ip $ip]
	set i 0
	set found 0

	while {$i < [llength $vhosts]} {
		set vhost [lindex $vhosts $i]

		if {[string equal -nocase $ip [lindex $vhost 0]]} {
			set vhosts [lreplace $vhosts $i $i]
			set found 1
			break
		}

		incr i
	}

	if {$found} {
		bncsetglobaltag vhosts $vhosts
	} else {
		return -code error "There is no such vhost."
	}
}

# iface commands
# +user
# getfreeip
# setvalue vhost
# getvhosts
# +admin
# addvhost ip limit host
# delvhost ip

proc iface-vhost:getfreeip {} {
	return [itype_string [vhost:findip]]
}

if {[info commands "registerifacecmd"] != ""} {
	registerifacecmd "vhost" "getfreeip" "iface-vhost:getfreeip"
}

proc iface-vhost:setvalue {setting value} {
	if {[iface:isoverride]} { return "" }

	if {[lsearch -exact [info commands] "lock:islocked"] != -1} {
		if {![string equal [lock:islocked [getctx] "vhost"] "0"]} { return "" }
	}

	if {![getbncuser [getctx] admin] && [string equal -nocase $setting "vhost"]} {
		set limit [vhost:getlimit $value]
		if {$limit == 0} { return -code error "You may not use this virtual host." }

		set count [vhost:countvhost $value]
		if {$count >= $limit} { return -code error "Sorry, the virtual host $ip is already being used by $count users. Please use another virtual host." }

		setbncuser [getctx] vhost $value
	}

	return ""
}

if {[info commands "registerifacecmd"] != ""} {
	registerifacecmd "vhost" "setvalue" "iface-vhost:setvalue"
}

proc iface-vhost:getvhosts {} {
	set result [itype_list_create]

	if {[lsearch -exact [info procs] "vhost_hack:getadditionalvhosts"] != -1} {
		set vhosts [concat $vhosts [vhost_hack:getadditionalvhosts]]

		foreach vhost $vhosts {
			set vhost_itype [itype_list_strings $vhost]
			itype_list_insert result $vhost_itype
		}
	}

	set vhosts [bncgetglobaltag vhosts]

	foreach vhost $vhosts {
		set vhost_itype [itype_list_strings_args [lindex $vhost 0] [vhost:countvhost [lindex $vhost 0]] [lindex $vhost 1] [lindex $vhost 2]]
		itype_list_insert result $vhost_itype
	}

	itype_list_end $result

	return $result
}

if {[info commands "registerifacecmd"] != ""} {
	registerifacecmd "vhost" "getvhosts" "iface-vhost:getvhosts"
}

proc iface-vhost:addvhost {ip limit host} {
	vhost:addvhost $ip $limit $host

	return ""
}

if {[info commands "registerifacecmd"] != ""} {
	registerifacecmd "vhost" "addvhost" "iface-vhost:addvhost" "access:admin"
}

proc iface-vhost:delvhost {ip} {
	vhost:delvhost $ip

	return ""
}

if {[info commands "registerifacecmd"] != ""} {
	registerifacecmd "vhost" "delvhost" "iface-vhost:delvhost" "access:admin"
}
