#! /usr/bin/tclsh

# $Id: uvtbot 46620 2017-05-31 10:49:44Z wsl $
# $URL: https://svn.uvt.nl/its-id/trunk/sources/uvtbot/uvtbot $
#
# Copyright (C) 2003-2006 Kees Leune http://www.leune.org/
#
# This program is part of uvtbot.
#
# uvtbot 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 (see COPYING); if not, check with
# http://www.gnu.org/copyleft/gpl.html.
#
#
# This program is meant to be used from inetd. It will accept an incoming IRC
# server connection on standard input, provided it sends the correct password.
# When the connection has been established, a bot will connect to a specified
# channel, where it will wait for instructions. There are two ways to use
# the bot: through ! commands by a user, or by an external process
# connecting to a TCP port.

# Default configuration settings.
# All of these can be overridden by the confuguration file.

set SETTINGS(ircsid)    4LC
set SETTINGS(ircport)   6667
set SETTINGS(irchost)   localhost
set SETTINGS(ircname)   localhost
set SETTINGS(channels)   {#nagios}
set SETTINGS(injchannels) {#nagios}
set SETTINGS(botnick)   alice
set SETTINGS(botname)   alice
set SETTINGS(debug)     0
set SETTINGS(ircd_in_passwd)  somesecretpassword
set SETTINGS(ircd_out_passwd) somesecretpassword
set SETTINGS(injport)    4321
set SETTINGS(logfile)   /var/log/uvtbot.log

set REGISTERED 0

# override any of above configuration settings in config file
source [lindex $argv 0]

# produce a timestamp for the IRC TS protocol
proc timestamp {} {
	return [clock seconds]
}

# produce a human readable timestamp
proc now {} {
	return "[clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}]"
}

# send msg to debug file of running in debug mode. Can be done a lot nicer
proc debug {msg} {
	global SETTINGS
	global LOGFILE

	if {!$SETTINGS(debug)} return

	if {!$LOGFILE} {
		set LOGFILE [open $SETTINGS(logfile) a]
		fconfigure LOGFILE -buffering line
	}

	puts $LOGFILE "[now] $msg"
}

# disconnect all connections and exit the program
proc disconnect {} {
	exit
}

# send a message to irc
proc sendToIrc {msg} {
	debug "To Irc> $msg"
	puts $msg
}

# handle incoming IRC messages
proc handleFromIrc {} {
	global SETTINGS

	if {[eof stdin]} {
		debug "Closing standard in"
		close stdin
		exit
	}

	gets stdin line
	debug "From IRC> $line"

	if {[regexp {^SERVER } $line]} {
		if {[regexp {^SERVER ([^ ]+) ([^ ]+) } $line x server passwd]} {
			if {[string equal $passwd $SETTINGS(ircd_in_passwd)]} {
				debug "Correct password from $server"
				registerAsIrcServer
			} else {
				debug "Incorrect password from remote server"
				disconnect
			}
		} else {
			debug "Incorrect SERVER from remote server"
			disconnect
		}
		return
	}

	if {[regexp {^:[^ ]+ PING ([^ ]+) ([^ ]+)$} $line x arg1 arg2]} {
		sendToIrc ":$SETTINGS(ircsid) PONG $arg2 $arg1"
		return
	}

	if {[regexp {^:[^ ]+ PING( .*)?$} $line x arg]} {
		sendToIrc ":$SETTINGS(ircsid) PONG$arg"
		return
	}

}

proc registerAsIrcServer {} {
	global SETTINGS

	set sid $SETTINGS(ircsid)
	set n $SETTINGS(botnick)
	set u $SETTINGS(botname)
	set h $SETTINGS(irchost)
	set i $SETTINGS(ircname)
	set c $SETTINGS(channels)

	sendToIrc "SERVER $i $SETTINGS(ircd_out_passwd) 0 $sid :uvtbot $n"
	sendToIrc ":$sid BURST [timestamp]"
	sendToIrc ":$sid VERSION :uvtbot"

	# :<sid of new users server> UID <uid> <timestamp> <nick> <hostname> <displayed-hostname> <ident> <ip> <signon time> +<modes {mode params}> :<gecos>
	sendToIrc ":$sid UID ${sid}AAAAAA 1 $n $h $h $u 0::1 [timestamp] + :$u"

	foreach chan $c {
		sendToIrc ":$i FJOIN $chan 1 +tnpsm :v,${sid}AAAAAA" 
	}

	sendToIrc ":$sid ENDBURST"

	global REGISTERED
	set REGISTERED 1
}

proc handleFromSocket {ch} {
	global CLIENTS

	if {[eof $ch] || [catch {
		gets $ch line
	}]} {
		debug "Closing connection from client at $CLIENTS($ch)"
		catch { close $ch }
		unset CLIENTS($ch)
		return
	}

	debug "Message received from client at $CLIENTS($ch)"

	global SETTINGS
	set sid $SETTINGS(ircsid)

	if {![string equal $line ""]} {
		foreach c $SETTINGS(injchannels) {
		   sendToIrc ":${sid}AAAAAA PRIVMSG $c :$line"
		}
	}
}

proc acceptIncoming {ch addr port} {
	global REGISTERED
	if {$REGISTERED} {
		global CLIENTS
		set CLIENTS($ch) $addr
		fconfigure $ch -buffering line
		fileevent $ch readable "handleFromSocket $ch"
		debug "Accepted incoming connection from client at $addr"
	} else {
		debug "Message received but handshake with IRC server not yet complete"
		catch { close $ch }
	}
}

fileevent stdin readable handleFromIrc
fconfigure stdin -buffering line
fconfigure stdout -buffering line
debug "Connected to Ircd."

sendToIrc "CAPAB START 1202"
sendToIrc "CAPAB CAPABILITIES :PROTOCOL=1202 IP6NATIVE=1 IP6SUPPORT=1"
sendToIrc "CAPAB CAPABILITIES :NICKMAX=256 HALFOP=1 CHANMAX=256 MAXMODES=256 IDENTMAX=256 MAXQUIT=65536 MAXTOPIC=65536 MAXKICK=65536 MAXGECOS=65536 MAXAWAY=65536"
sendToIrc "CAPAB END"

if {[catch {
	socket -server acceptIncoming $SETTINGS(injport)
}]} {
	debug "Unable to open socket on port $SETTINGS(injport)"
} else {
	debug "Listening on port $SETTINGS(injport) and waiting for incoming connections."
	vwait STOP
}
