hubsec nick opt, ctcp before join, delay join until ctcp version (with timeout), mirc/atomic-specific hack
--HG-- branch : nmdc-ircfrontend
This commit is contained in:
parent
169debea93
commit
7bf832322d
3
main.go
3
main.go
@ -30,8 +30,10 @@ func main() {
|
||||
ircAddress := flag.String("bind", ":6667", "The address:port to bind to and listen for clients on")
|
||||
dcAddress := flag.String("upstream", "127.0.0.1:411", "Upstream NMDC server")
|
||||
serverName := flag.String("servername", "nmdc-ircfrontend", "Server name displayed to clients")
|
||||
hubsec := flag.String("hubsecurity", "Hub-Security", "Nick used for administrative events")
|
||||
verbose := flag.Bool("verbose", false, "Display debugging information")
|
||||
autojoin := flag.Bool("autojoin", true, "Automatically join clients to the channel")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
log.Printf("Listening on '%s'...", *ircAddress)
|
||||
@ -56,6 +58,7 @@ func main() {
|
||||
server := NewServer(*serverName, libnmdc.HubAddress(*dcAddress), conn)
|
||||
server.verbose = *verbose
|
||||
server.autojoin = *autojoin
|
||||
server.hubSecNick = *hubsec
|
||||
go server.RunWorker()
|
||||
}
|
||||
}
|
||||
|
246
server.go
246
server.go
@ -27,6 +27,7 @@ import (
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -39,11 +40,13 @@ const (
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
name string
|
||||
motd string
|
||||
name string
|
||||
motd string
|
||||
hubSecNick string
|
||||
|
||||
clientConn net.Conn
|
||||
clientState ClientState
|
||||
clientConn net.Conn
|
||||
clientState ClientState
|
||||
ClientStateLock sync.Mutex
|
||||
|
||||
upstreamLauncher libnmdc.HubConnectionOptions
|
||||
upstreamCloser chan struct{}
|
||||
@ -52,7 +55,8 @@ type Server struct {
|
||||
verbose bool
|
||||
autojoin bool
|
||||
|
||||
sentCtcpVersion bool
|
||||
recievedFirstServerMessage bool
|
||||
recievedCtcpVersion bool
|
||||
}
|
||||
|
||||
func NewServer(name string, upstream libnmdc.HubAddress, conn net.Conn) *Server {
|
||||
@ -153,10 +157,14 @@ func (s *Server) RunWorker() {
|
||||
|
||||
func (s *Server) postGeneralMessageInRoom(msg string) {
|
||||
|
||||
// FIXME blank names work well in hexchat, but not in yaaic
|
||||
var BLANK_NICK = "" // "!@" + s.name // "PtokaX" // "\xC2\xA0"
|
||||
var sendAs = ""
|
||||
|
||||
// CTCP action conversion
|
||||
// Some clients can't handle blank nicks very well
|
||||
if s.upstreamLauncher.Self.ClientTag == "mIRC" || s.upstreamLauncher.Self.ClientTag == "Atomic" {
|
||||
sendAs = s.hubSecNick + "!" + s.hubSecNick + "@" + s.hubSecNick
|
||||
}
|
||||
|
||||
// Detect pseudo-system message for potential CTCP ACTION conversion
|
||||
words := strings.Split(msg, " ")
|
||||
firstWord := words[0]
|
||||
remainder := strings.Join(words[1:], " ")
|
||||
@ -177,7 +185,7 @@ func (s *Server) postGeneralMessageInRoom(msg string) {
|
||||
|
||||
} else {
|
||||
// genuine system message
|
||||
s.reply(rplMsg, BLANK_NICK, BLESSED_CHANNEL, msg)
|
||||
s.reply(rplMsg, sendAs, BLESSED_CHANNEL, msg)
|
||||
}
|
||||
|
||||
}
|
||||
@ -253,12 +261,10 @@ func (s *Server) upstreamWorker() {
|
||||
} else {
|
||||
// nick!username@userhost, but for us all three of those are always identical
|
||||
s.reply(rplMsg, hubEvent.Nick+"!"+hubEvent.Nick+"@"+hubEvent.Nick, BLESSED_CHANNEL, reformatIncomingMessageBody(hubEvent.Message))
|
||||
}
|
||||
|
||||
if !s.sentCtcpVersion {
|
||||
// Abuse this first username in order to also send a CTCP version request
|
||||
s.reply(rplMsg, hubEvent.Nick+"!"+hubEvent.Nick+"@"+hubEvent.Nick, BLESSED_CHANNEL, "\x01VERSION\x01")
|
||||
s.sentCtcpVersion = true
|
||||
}
|
||||
if !s.recievedFirstServerMessage {
|
||||
s.hubSecNick = hubEvent.Nick // Replace with the hub's real Hub-Security nick, although we shouldn't need it again
|
||||
}
|
||||
|
||||
case libnmdc.EVENT_SYSTEM_MESSAGE_FROM_CONN, libnmdc.EVENT_SYSTEM_MESSAGE_FROM_HUB:
|
||||
@ -356,7 +362,9 @@ func (s *Server) handleCommand(command string, args []string) {
|
||||
s.reply(rplWelcome)
|
||||
s.clientState = CSRegistered
|
||||
|
||||
// TODO tell the client that they /must/ join #chat
|
||||
// Send CTCP VERSION request immediately
|
||||
s.reply(rplMsg, s.hubSecNick+"!"+s.hubSecNick+"@"+s.hubSecNick, BLESSED_CHANNEL, "\x01VERSION\x01")
|
||||
|
||||
if s.autojoin {
|
||||
s.handleCommand("JOIN", []string{BLESSED_CHANNEL})
|
||||
}
|
||||
@ -382,101 +390,6 @@ func (s *Server) handleRegisteredCommand(command string, args []string) {
|
||||
}
|
||||
s.reply(rplListEnd)
|
||||
|
||||
case "JOIN":
|
||||
if len(args) < 1 {
|
||||
s.reply(errMoreArgs)
|
||||
return
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
case BLESSED_CHANNEL:
|
||||
if s.clientState != CSJoined {
|
||||
// Join for the first time
|
||||
s.clientState = CSJoined
|
||||
|
||||
// Acknowledge
|
||||
s.reply(rplJoin, s.clientNick(), BLESSED_CHANNEL)
|
||||
|
||||
// Send (initially just us) nicklist for the chat channel
|
||||
//s.sendNames()
|
||||
|
||||
// Spawn upstream connection
|
||||
s.upstream = s.upstreamLauncher.Connect()
|
||||
go s.upstreamWorker()
|
||||
|
||||
} else {
|
||||
// They're already here, ignore
|
||||
// Can happen if autojoin is enabled but the client already requested a login
|
||||
}
|
||||
|
||||
case "0":
|
||||
// Quitting all channels? Drop client
|
||||
s.reply(rplKill, "Bye.")
|
||||
s.DisconnectClient()
|
||||
|
||||
default:
|
||||
s.reply(rplKill, "There is only '"+BLESSED_CHANNEL+"'.")
|
||||
s.DisconnectClient()
|
||||
}
|
||||
|
||||
default:
|
||||
s.handleJoinedCommand(command, args)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) SetClientSoftwareVersion(ver string) {
|
||||
// "HexChat 2.12.1 [x64] / Microsoft Windows 10 Pro (x64) [Intel(R) Core(TM) i5-2500 CPU @ 3.30GHz (3.60GHz)]"
|
||||
// "AndroIRC - Android IRC Client (5.2 - Build 6830152) - http://www.androirc.com"
|
||||
// "AndChat 1.4.3.2 http://www.andchat.net"
|
||||
// "liteIRC for Android 1.1.8"
|
||||
// ":Relay:1.0:Android"
|
||||
|
||||
// A bit long and unwieldy.
|
||||
// Heuristic: keep the first word, and the the first word containing digits
|
||||
|
||||
tag := APP_NAME
|
||||
version := APP_VERSION
|
||||
|
||||
words := strings.Split(ver, " ")
|
||||
|
||||
for _, word := range words[1:] {
|
||||
if strings.ContainsAny(word, "0123456789") {
|
||||
tag = words[0]
|
||||
version = strings.Replace(word, "(", "", -1) // AndroIRC
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
s.verbosef("Replacing client tag with '%s' version '%s'", tag, version)
|
||||
|
||||
s.upstreamLauncher.Self.ClientTag = tag
|
||||
s.upstreamLauncher.Self.ClientVersion = version
|
||||
s.upstream.Hco.Self.ClientTag = tag
|
||||
s.upstream.Hco.Self.ClientVersion = version
|
||||
|
||||
s.upstream.SayInfo()
|
||||
}
|
||||
|
||||
func (s *Server) handleJoinedCommand(command string, args []string) {
|
||||
|
||||
if s.clientState != CSJoined {
|
||||
s.reply(errNotReg)
|
||||
return
|
||||
}
|
||||
|
||||
switch command {
|
||||
case "PART":
|
||||
if len(args) < 1 {
|
||||
s.reply(errMoreArgs)
|
||||
return
|
||||
}
|
||||
|
||||
if args[0] == BLESSED_CHANNEL {
|
||||
// You can check out any time you like, but you can never leave
|
||||
s.reply(rplJoin, s.clientNick(), BLESSED_CHANNEL)
|
||||
s.sendNames()
|
||||
}
|
||||
|
||||
case "PRIVMSG", "NOTICE":
|
||||
if len(args) < 2 {
|
||||
s.reply(errMoreArgs)
|
||||
@ -520,6 +433,119 @@ func (s *Server) handleJoinedCommand(command string, args []string) {
|
||||
|
||||
}
|
||||
|
||||
case "JOIN":
|
||||
if len(args) < 1 {
|
||||
s.reply(errMoreArgs)
|
||||
return
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
case BLESSED_CHANNEL:
|
||||
// Give it a few seconds - it's better to hear the CTCP VERSION
|
||||
// response first. Once we get that, it'll connect instantly
|
||||
go func() {
|
||||
<-time.After(WAIT_FOR_VERSION * time.Second)
|
||||
s.maybeStartUpstream()
|
||||
}()
|
||||
|
||||
case "0":
|
||||
// Quitting all channels? Drop client
|
||||
s.reply(rplKill, "Bye.")
|
||||
s.DisconnectClient()
|
||||
|
||||
default:
|
||||
s.reply(rplKill, "There is only '"+BLESSED_CHANNEL+"'.")
|
||||
s.DisconnectClient()
|
||||
}
|
||||
|
||||
default:
|
||||
s.handleJoinedCommand(command, args)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) maybeStartUpstream() {
|
||||
s.ClientStateLock.Lock()
|
||||
defer s.ClientStateLock.Unlock()
|
||||
|
||||
if s.clientState != CSJoined {
|
||||
// Join for the first time
|
||||
s.clientState = CSJoined
|
||||
|
||||
// Acknowledge
|
||||
s.reply(rplJoin, s.clientNick(), BLESSED_CHANNEL)
|
||||
|
||||
// Spawn upstream connection
|
||||
s.upstream = s.upstreamLauncher.Connect()
|
||||
go s.upstreamWorker()
|
||||
|
||||
} else {
|
||||
// They're already here, ignore
|
||||
// Can happen if autojoin is enabled but the client already requested a login
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) SetClientSoftwareVersion(ver string) {
|
||||
// "HexChat 2.12.1 [x64] / Microsoft Windows 10 Pro (x64) [Intel(R) Core(TM) i5-2500 CPU @ 3.30GHz (3.60GHz)]"
|
||||
// "AndroIRC - Android IRC Client (5.2 - Build 6830152) - http://www.androirc.com"
|
||||
// "AndChat 1.4.3.2 http://www.andchat.net"
|
||||
// "liteIRC for Android 1.1.8"
|
||||
// ":Relay:1.0:Android"
|
||||
|
||||
// A bit long and unwieldy.
|
||||
// Heuristic: keep the first word, and the the first word containing digits
|
||||
|
||||
tag := APP_NAME
|
||||
version := APP_VERSION
|
||||
|
||||
words := strings.Split(ver, " ")
|
||||
|
||||
for _, word := range words[1:] {
|
||||
if strings.ContainsAny(word, "0123456789") {
|
||||
tag = words[0]
|
||||
version = strings.Replace(word, "(", "", -1) // AndroIRC
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
s.verbosef("Replacing client tag with '%s' version '%s'", tag, version)
|
||||
|
||||
s.upstreamLauncher.Self.ClientTag = tag
|
||||
s.upstreamLauncher.Self.ClientVersion = version
|
||||
s.recievedCtcpVersion = true
|
||||
|
||||
if s.upstream != nil {
|
||||
s.upstream.Hco.Self.ClientTag = tag
|
||||
s.upstream.Hco.Self.ClientVersion = version
|
||||
s.upstream.SayInfo()
|
||||
|
||||
} else {
|
||||
// Connected for the first time (order was CTCP VERSION --> JOIN)
|
||||
s.maybeStartUpstream()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *Server) handleJoinedCommand(command string, args []string) {
|
||||
|
||||
if s.clientState != CSJoined {
|
||||
s.reply(errNotReg)
|
||||
return
|
||||
}
|
||||
|
||||
switch command {
|
||||
case "PART":
|
||||
if len(args) < 1 {
|
||||
s.reply(errMoreArgs)
|
||||
return
|
||||
}
|
||||
|
||||
if args[0] == BLESSED_CHANNEL {
|
||||
// You can check out any time you like, but you can never leave
|
||||
s.reply(rplJoin, s.clientNick(), BLESSED_CHANNEL)
|
||||
s.sendNames()
|
||||
}
|
||||
|
||||
case "QUIT":
|
||||
s.DisconnectClient()
|
||||
|
||||
|
@ -27,6 +27,7 @@ const (
|
||||
NICKS_PER_PROTOMSG = 128 // Max number of nicks to send per message
|
||||
CLIENT_READ_BUFFSIZE = 512
|
||||
CLIENT_KEEPALIVE_EVERY = 60 // should be longer than the client's one, hexchat is 30s
|
||||
WAIT_FOR_VERSION = 3 // Seconds to wait for the CTCP VERSION response before connecting with default MyINFO
|
||||
)
|
||||
|
||||
type replyCode int
|
||||
|
Loading…
Reference in New Issue
Block a user