#! /bin/sh
# 
# $Id: uvtbot 38027 2012-10-22 10:16:56Z wsl $
# $URL: https://svn.uvt.nl/its-id/trunk/sources/uvtbot/source/src/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.
#
#\
exec tclsh "$0" "$@"

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

set SETTINGS(ircport)   6667
set SETTINGS(irchost)   localhost
set SETTINGS(ircname)   localhost
set SETTINGS(channels)   {#nagios}
set SETTINGS(injchannels) {#nagios}
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

# 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]
} ;# end timestamp

# produce a human readable timestamp
proc now {} {
    return "\[[clock format [clock seconds] -format {%D %R:%S}]\]"
} ;# end now

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

    if {$SETTINGS(debug)==0} return;

    set f [open $SETTINGS(logfile) a]
    puts $f "[now] $msg"
    close $f
} ;# end debug


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

    if {[info exists CHANNELS(hyper)]} {
        catch {
            puts $CHANNELS(hyper) "QUIT:::"
            close $CHANNELS(hyper)
        }

        debug "Connection to Hyperborea closed."
    }

    if {[info exists CHANNELS(irc)]} {
        catch {close $CHANNELS(irc)}
        debug "Connection to Irc closed."
    }

    exit
} ;# end disconncet


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


# 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 {^(:[^ ]+)? ?([[:alnum:]]+) :?(.*)$} $line x prefix \
        cmd data]} {
        debug "DEBUG: Ignoring line $line"
        return
    }
    if {[regexp {:(.*)} $prefix x sender]} {
        debug "Sender set to $sender"
    } else {
        set sender $prefix
        debug "Sender set to $sender"
    }

    switch $cmd {
        PASS {
            debug "Incoming password"
            set tuple [split $data]
            set passwd [lindex $tuple 0]
            if {![string match $passwd $SETTINGS(ircd_in_passwd)]} {
                debug "Incorrect password from remote server"
                disconnect
                return
            }
            debug "Correct password"
            registerAsIrcServer
        }

        PING {
            # PING :entropy.uvt.nl
            sendToIrc "PONG :$data"
        }

    }
} ;# end handleFromIRC


proc registerAsIrcServer {} {
    global SETTINGS

    sendToIrc "PASS $SETTINGS(ircd_out_passwd) :TS"
    sendToIrc "SERVER $SETTINGS(ircname) 1 :[info hostname]"

    set u $SETTINGS(botname)
    set h $SETTINGS(irchost)
    set i $SETTINGS(ircname)
    set c $SETTINGS(channels)

    # :sender NICK nickname hopcount TS umode username hostname server ircname
    # NICK uvtbot 1 1064171082 +i uvtbot localhost uvtbot :uvtbot
    sendToIrc "NICK $u 1 [timestamp] +i $u $h $i :$u"

    # :sender SJOIN TS channel modes flags+nick
    # :entropy.uvt.nl SJOIN 1064171082 #hyperborea +tn :kees 
    foreach chan $c {
        sendToIrc ":$i SJOIN [timestamp] $chan +tnpsm :+$u" 
    }
}

# connect to $host on port $port
proc connect {host port} {
    if {[catch {
        set s [socket $host $port]
    }]} {
        debug "Unable to connect to $host $port"
        disconnect
    }

    fconfigure $s -buffering line

    return $s
} ;# end connect

# Connect to IRC
proc connectToIrc {} {
    global CHANNELS
    global SETTINGS

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

} ;# end connectToIrc


proc handleFromSocket {s} {
    global SETTINGS
    global CHANNELS

    while {![eof $s]} {
        if {[catch {
            gets $s line
        }]} { break }

        lappend msg $line
    }
    close $s

    debug "Message received from client at $CHANNELS($s)"
    # set channel $SETTINGS(channel)

    foreach l $msg {
        foreach c $SETTINGS(injchannels) {
           sendToIrc ":$SETTINGS(botname) PRIVMSG $c :$l"
        }
    }
} ;# end handleFromSocket


proc acceptIncoming {ch addr port} {
    global CHANNELS

    fconfigure $ch -buffering line
    fileevent $ch readable "handleFromSocket $ch"
    set  CHANNELS($ch) $addr

    debug "Accepted incoming connection from client at $addr"
} ;# end acceptIncoming

#===============================


connectToIrc

if {[catch {
    set s [socket -server acceptIncoming $SETTINGS(injport)]
}]} {
    debug "Unable to open socket on port $SETTINGS(injport)"
    exit
}

debug "Listening on port $SETTINGS(injport) and waiting for incoming connections."

vwait STOP
