IRC gateway
Create an IRC Gateway capable of connecting an IRC server with another IRC server or Chat server
Go
Just a bare-bones gateway.
<lang go>package main
import (
"crypto/tls" "fmt" "github.com/thoj/go-ircevent" "log" "os"
)
func main() {
if len(os.Args) != 9 { fmt.Println("To use this gateway, you need to pass 8 command line arguments, namely:") fmt.Println(" <server1> <channel1> <nick1> <user1> <server2> <channel2> <nick2> <user2>") return } server1, channel1, nick1, user1 := os.Args[1], os.Args[2], os.Args[3], os.Args[4] server2, channel2, nick2, user2 := os.Args[5], os.Args[6], os.Args[7], os.Args[8]
irc1 := irc.IRC(nick1, user1) irc1.VerboseCallbackHandler = true irc1.Debug = false irc1.UseTLS = true irc1.TLSConfig = &tls.Config{InsecureSkipVerify: true}
irc2 := irc.IRC(nick2, user2) irc2.VerboseCallbackHandler = true irc2.Debug = false irc2.UseTLS = true irc2.TLSConfig = &tls.Config{InsecureSkipVerify: true}
irc1.AddCallback("001", func(e *irc.Event) { irc1.Join(channel1) msg := fmt.Sprintf("<gateway> Hello %s. Please send your first message to %s.", nick1, nick2) irc1.Privmsg(nick1, msg) log.Println(msg) })
irc1.AddCallback("366", func(e *irc.Event) {})
irc1.AddCallback("PRIVMSG", func(e *irc.Event) { msg := fmt.Sprintf("<%s> %s", nick1, e.Message) irc2.Privmsg(nick2, msg) log.Println(msg) })
irc2.AddCallback("001", func(e *irc.Event) { irc2.Join(channel2) msg := fmt.Sprintf("<gateway> Hello %s. Please wait for your first message from %s.", nick2, nick1) irc2.Privmsg(nick2, msg) log.Println(msg) })
irc2.AddCallback("366", func(e *irc.Event) {})
irc2.AddCallback("PRIVMSG", func(e *irc.Event) { msg := fmt.Sprintf("<%s> %s", nick2, e.Message) irc1.Privmsg(nick1, msg) log.Println(msg) })
err1 := irc1.Connect(server1) if err1 != nil { log.Fatal(err1) }
err2 := irc2.Connect(server2) if err2 != nil { log.Fatal(err2) }
go irc2.Loop() irc1.Loop()
}</lang>
Phix
For use with Chat_server#Phix
-- -- demo\rosetta\IRC_Gateway.exw -- ============================ -- -- Run ChatServer first, then this, then ChatClient.exw with bViaGateway set to true. -- -- Translation between the various IRC protocols left as an exercise for the reader, -- this is just a simple passthrough service. -- -- Also this uses a simpler server <--> gateway <--> {clients} model, rather than -- a more sensible server <-> {gateway1 <-> client1, gateway2 <-> client2} model, -- probably easily changed, see the {ci,gi} = connection[i] etc comments below. -- Hence as is this will broadcast eg /nt (name taken) rather than send it to a -- single specific client, which would obviously be better. -- without js constant dl = `Download rosetta\eulibnet\ from http://phix.x10.mx/pmwiki/pmwiki.php?n=Main.Eulibnet` assert(get_file_type("eulibnet")=FILETYPE_DIRECTORY,dl) include eulibnet/eulibnet.ew atom gateway_listconn, server_listconn constant IP = "127.0.0.1", server_port = "29029", gateway_port = "29030", server_address = IP & ":" & server_port, gateway_address = IP & ":" & gateway_port, timeout = 20, MAX_MSG = 550, Escape = #1B sequence connections = {} -- (inbound on the gateway port) -- (should maybe {inbound,outbound}) procedure message(string msg, sequence args={}) if length(args) then msg = sprintf(msg,args) end if printf(1,"%s\n",{msg}) end procedure procedure shutDown() message("Shutting down euLibnet...") for i = 1 to length(connections) do --DEV/SUG -- integer {ci,gi} = connections[i] -- if net_closeconn(ci) then crash("Error closing client connection!") end if -- if net_closeconn(gi) then crash("Error closing gateway connection!") end if if net_closeconn(connections[i]) then crash("Error closing connection!") end if end for if net_closeconn(gateway_listconn) then crash("Error closing gateway_listconn!") end if if net_closeconn(server_listconn) then crash("Error closing server_listconn!") end if if net_shutdown() then crash("Error shutting down euLibnet!") end if end procedure --DEV to go if {ci,gi} model adopted... probably procedure sendToAll(string msg) -- Send msg to all clients for i=1 to length(connections) do atom ci = connections[i] message("Sending to connection %d",{ci}) if net_send_rdm(ci, msg) then message("Error sending to connection %d",{ci}) end if end for end procedure message("Initializing euLibnet...") if net_init() then crash("Error initializing euLibnet!") end if message("done.") message("Initializing driver...") if net_initdriver(NET_DRIVER_WSOCK_WIN) != 1 then crash("Error initializing WinSock driver!") end if message("done.") message("Opening port " & gateway_address & "...") gateway_listconn = net_openconn(NET_DRIVER_WSOCK_WIN, gateway_address) if gateway_listconn = NULL then crash("Couldn't open connection (gateway already running?)") end if message("done.") if net_listen(gateway_listconn) then crash("Error trying to listen to port") end if message("Listening on port " & gateway_address) --DEV some/all probably better as {ci,gi} in connections[i]: message("Opening server connection...") server_listconn = net_openconn(NET_DRIVER_WSOCK_WIN, NULL) if server_listconn = NULL then crash("Couldn't open connection.") end if message("done.") message("Attempting to connect to chat server...") integer ret = net_connect_wait_time(server_listconn, server_address, timeout) if ret < 0 then crash("Error trying to establish connection.") elsif ret > 0 then crash("Timeout trying to establish connection.") end if message("done.") -- main loop (poll until Escape keyed) while get_key()!=Escape do integer conn = net_poll_listen(gateway_listconn) -- integer ci = net_poll_listen(gateway_listconn), gi if conn != NULL then --DEV make a new gateway connection to the server here...? -- gi = <as server_listconn above?> -- connections = append(connections, {ci,gi}) connections = append(connections, conn) message("New connection open from " & net_getpeer(conn)) end if -- Check for messages from clients for i=1 to length(connections) do integer ci = connections[i] -- {ci,gi} = connections[i] if net_query_rdm(ci) > 0 then --Get the message sequence msg = net_receive_rdm(ci, MAX_MSG) message("received msg \"%s\" of length %d from %d",{msg[2],msg[1],ci}) if msg[1] < 0 then --Exit on error {} = net_ignore_rdm(ci) crash("Server error: some data may be lost") end if msg = msg[2] message("Sending to server") if net_send_rdm(server_listconn, msg) then -- if net_send_rdm(gi, msg) then crash("Error sending to server") end if end if -- <as below but for gi instead of server_listcomm here?> end for if net_query_rdm(server_listconn) > 0 then --Check for message -- if net_query_rdm(gi) > 0 then --Check for message sequence msg = net_receive_rdm(server_listconn, MAX_MSG) -- sequence msg = net_receive_rdm(gi, MAX_MSG) if msg[1] < 0 then message("Error receiving message!") {} = net_ignore_rdm(server_listconn) -- {} = net_ignore_rdm(gi) end if msg = msg[2] message(msg) sendToAll(msg) --DEV maybe instead: -- if net_send_rdm(ci, msg) then -- crash("Error sending to clent") -- end if end if sleep(1) end while shutDown() --?"done" --{} = wait_key()
Tcl
This code is called as a complete script, perhaps like this: <lang sh>./ircgateway.tcl irc://hostA.org/fishing bait irc://hostB.com:6667/haxors botfly</lang>
<lang tcl>#!/bin/env tclsh8.5 package require picoirc
- Parse script arguments
- URL form: irc://foobar.org/secret
if {$argc != 4} {
puts stderr "wrong # args: should be \"$argv0 ircA nickA ircB nickB\"" exit 1
} lassign $argv url1 nick1 url2 nick2
- How to do the forwarding from one side to the other
proc handle {from to -> state args} {
upvar #0 conn($from) f conn($to) t chan($to) chan switch -exact -- $state {
"chat" { lassign $args target nick message type if {![string match "*>>*<<*" $message]} { picoirc::post $t $chan ">>$nick said<< $message" } } "traffic" { lassign $args action channel nick newnick switch -exact -- $action { "entered" - "left" { picoirc::post $t $chan ">>$nick has $action<<" } } } "close" { exit }
}
}
- Connect and run the event loop
set chan(1) [lindex [picoirc::splituri $url1] 2] set chan(2) [lindex [picoirc::splituri $url1] 2] interp alias {} handle1to2 {} handle 1 2 interp alias {} handle2to1 {} handle 2 1 set conn(1) [picoirc::connect handle1to2 $nick1 $url1] set conn(2) [picoirc::connect handle2to1 $nick2 $url2] vwait forever</lang>
Wren
An embedded application with a Go host so we can use the 'go-ircevent' library.
As it's not possible to access command line arguments directly from Wren when it is being embedded, we instead ask the gateway user to input details of the connections needed. <lang ecmascript>/* irc_gateway.wren */
import "./dynamic" for Tuple
var Connection = Tuple.create("Connection", ["server", "channel", "nick", "user"])
foreign class IRC {
construct new(number, nick, user) {} foreign connect(server) foreign verboseCallbackHandler=(arg) foreign debug=(arg) foreign useTLS=(arg) foreign configTLS=(arg) foreign addCallback(number, code, msg, channel, nick, otherNick)
}
foreign class Reader {
construct new() {} foreign readLine()
}
var reader = Reader.new() var Connections = List.filled(2, null) System.print("To use this gateway, please enter the following:\n") for (i in 0..1) {
System.print("Details for connection %(i+1):") System.write(" Server : ") var server = reader.readLine() System.write(" Channel : ") var channel = reader.readLine() System.write(" Nickname : ") var nick = reader.readLine() System.write(" User : ") var user = reader.readLine() Connections[i] = Connection.new(server, channel, nick, user) System.print()
}
for (i in 0..1) {
var c = Connections[i] var irc = IRC.new(i, c.nick, c.user) irc.verboseCallbackHandler = true irc.debug = false irc.useTLS = true irc.configTLS = true var otherNick = (i == 0) ? Connections[1].nick : Connections[0].nick var msg if (i == 0) { msg = "<gateway> Hello %(c.nick). Please send your first message to %(otherNick)." } else { msg = "<gateway> Hello %(c.nick). Please wait for your first message from %(otherNick)." } irc.addCallback(i, "001", msg, c.channel, c.nick, otherNick) irc.addCallback(i, "366", "" , c.channel, c.nick, otherNick) irc.addCallback(i, "PRIVMSG", "" , c.channel, c.nick, otherNick) irc.connect(c.server)
}</lang> We now embed this script in the following Go program and run it from a terminal. To close the gateway just press Ctrl-C. <lang go>/* go run irc_gateway.go */
package main
import (
"bufio" "crypto/tls" "fmt" wren "github.com/crazyinfin8/WrenGo" "github.com/thoj/go-ircevent" "log" "os" "strings"
)
type any = interface{} // not needed if using Go v1.18 or later
var ircObjs [2]*irc.Connection
func newIRC(vm *wren.VM, parameters []any) (any, error) {
number := int(parameters[1].(float64)) nick := parameters[2].(string) user := parameters[3].(string) ircObjs[number] = irc.IRC(nick, user) return &ircObjs[number], nil
}
func connect(vm *wren.VM, parameters []any) (any, error) {
handle := parameters[0].(*wren.ForeignHandle) ifc, _ := handle.Get() ircObj := *(ifc.(**irc.Connection)) server := parameters[1].(string) err := ircObj.Connect(server) if err != nil { log.Fatal(err) } return nil, nil
}
func setVerboseCallbackHandler(vm *wren.VM, parameters []any) (any, error) {
handle := parameters[0].(*wren.ForeignHandle) ifc, _ := handle.Get() ircObj := *(ifc.(**irc.Connection)) value := parameters[1].(bool) ircObj.VerboseCallbackHandler = value return nil, nil
}
func setDebug(vm *wren.VM, parameters []any) (any, error) {
handle := parameters[0].(*wren.ForeignHandle) ifc, _ := handle.Get() ircObj := *(ifc.(**irc.Connection)) value := parameters[1].(bool) ircObj.Debug = value return nil, nil
}
func setUseTLS(vm *wren.VM, parameters []any) (any, error) {
handle := parameters[0].(*wren.ForeignHandle) ifc, _ := handle.Get() ircObj := *(ifc.(**irc.Connection)) value := parameters[1].(bool) ircObj.UseTLS = value return nil, nil
}
func setConfigTLS(vm *wren.VM, parameters []any) (any, error) {
handle := parameters[0].(*wren.ForeignHandle) ifc, _ := handle.Get() ircObj := *(ifc.(**irc.Connection)) value := parameters[1].(bool) ircObj.TLSConfig = &tls.Config{InsecureSkipVerify: value} return nil, nil
}
func addCallback(vm *wren.VM, parameters []any) (any, error) {
handle := parameters[0].(*wren.ForeignHandle) ifc, _ := handle.Get() ircObj := *(ifc.(**irc.Connection)) number := int(parameters[1].(float64)) code := parameters[2].(string) msg := parameters[3].(string) channel := parameters[4].(string) nick := parameters[5].(string) otherNick := parameters[6].(string) if code == "001" { ircObj.AddCallback("001", func(e *irc.Event) { ircObj.Join(channel) ircObj.Privmsg(nick, msg) log.Println(msg) }) } else if code == "366" { ircObj.AddCallback("366", func(e *irc.Event) {}) } else if code == "PRIVMSG" { ircObj.AddCallback("PRIVMSG", func(e *irc.Event) { msg := fmt.Sprintf("<%s> %s", nick, e.Message) if number == 0 { ircObjs[1].Privmsg(otherNick, msg) } else { ircObjs[0].Privmsg(otherNick, msg) } log.Println(msg) }) } return nil, nil
}
func newReader(vm *wren.VM, parameters []any) (any, error) {
reader := bufio.NewReader(os.Stdin) return &reader, nil
}
func readLine(vm *wren.VM, parameters []any) (any, error) {
handle := parameters[0].(*wren.ForeignHandle) ifc, _ := handle.Get() bufin := *(ifc.(**bufio.Reader)) s, _ := bufin.ReadString('\n') // includes the delimiter return s[:len(s)-1], nil
}
func moduleFn(vm *wren.VM, name string) (string, bool) {
if name != "meta" && name != "random" && !strings.HasSuffix(name, ".wren") { name += ".wren" } return wren.DefaultModuleLoader(vm, name)
}
func main() {
cfg := wren.NewConfig() cfg.LoadModuleFn = moduleFn vm := cfg.NewVM() var fileName = "irc_gateway.wren" IRCMethodMap := wren.MethodMap{ "connect(_)": connect, "verboseCallbackHandler=(_)": setVerboseCallbackHandler, "debug=(_)": setDebug, "useTLS=(_)": setUseTLS, "configTLS=(_)": setConfigTLS, "addCallback(_,_,_,_,_,_)": addCallback, }
readerMethodMap := wren.MethodMap{"readLine()": readLine}
classMap := wren.ClassMap{ "IRC": wren.NewClass(newIRC, nil, IRCMethodMap), "Reader": wren.NewClass(newReader, nil, readerMethodMap), }
module := wren.NewModule(classMap) vm.SetModule(fileName, module) vm.InterpretFile(fileName) go ircObjs[1].Loop() ircObjs[0].Loop() vm.Free()
}</lang>