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")
|
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")
|
dcAddress := flag.String("upstream", "127.0.0.1:411", "Upstream NMDC server")
|
||||||
serverName := flag.String("servername", "nmdc-ircfrontend", "Server name displayed to clients")
|
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")
|
verbose := flag.Bool("verbose", false, "Display debugging information")
|
||||||
autojoin := flag.Bool("autojoin", true, "Automatically join clients to the channel")
|
autojoin := flag.Bool("autojoin", true, "Automatically join clients to the channel")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
log.Printf("Listening on '%s'...", *ircAddress)
|
log.Printf("Listening on '%s'...", *ircAddress)
|
||||||
@ -56,6 +58,7 @@ func main() {
|
|||||||
server := NewServer(*serverName, libnmdc.HubAddress(*dcAddress), conn)
|
server := NewServer(*serverName, libnmdc.HubAddress(*dcAddress), conn)
|
||||||
server.verbose = *verbose
|
server.verbose = *verbose
|
||||||
server.autojoin = *autojoin
|
server.autojoin = *autojoin
|
||||||
|
server.hubSecNick = *hubsec
|
||||||
go server.RunWorker()
|
go server.RunWorker()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
246
server.go
246
server.go
@ -27,6 +27,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,11 +40,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
name string
|
name string
|
||||||
motd string
|
motd string
|
||||||
|
hubSecNick string
|
||||||
|
|
||||||
clientConn net.Conn
|
clientConn net.Conn
|
||||||
clientState ClientState
|
clientState ClientState
|
||||||
|
ClientStateLock sync.Mutex
|
||||||
|
|
||||||
upstreamLauncher libnmdc.HubConnectionOptions
|
upstreamLauncher libnmdc.HubConnectionOptions
|
||||||
upstreamCloser chan struct{}
|
upstreamCloser chan struct{}
|
||||||
@ -52,7 +55,8 @@ type Server struct {
|
|||||||
verbose bool
|
verbose bool
|
||||||
autojoin bool
|
autojoin bool
|
||||||
|
|
||||||
sentCtcpVersion bool
|
recievedFirstServerMessage bool
|
||||||
|
recievedCtcpVersion bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(name string, upstream libnmdc.HubAddress, conn net.Conn) *Server {
|
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) {
|
func (s *Server) postGeneralMessageInRoom(msg string) {
|
||||||
|
|
||||||
// FIXME blank names work well in hexchat, but not in yaaic
|
var sendAs = ""
|
||||||
var BLANK_NICK = "" // "!@" + s.name // "PtokaX" // "\xC2\xA0"
|
|
||||||
|
|
||||||
// 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, " ")
|
words := strings.Split(msg, " ")
|
||||||
firstWord := words[0]
|
firstWord := words[0]
|
||||||
remainder := strings.Join(words[1:], " ")
|
remainder := strings.Join(words[1:], " ")
|
||||||
@ -177,7 +185,7 @@ func (s *Server) postGeneralMessageInRoom(msg string) {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// genuine system message
|
// 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 {
|
} else {
|
||||||
// nick!username@userhost, but for us all three of those are always identical
|
// 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))
|
s.reply(rplMsg, hubEvent.Nick+"!"+hubEvent.Nick+"@"+hubEvent.Nick, BLESSED_CHANNEL, reformatIncomingMessageBody(hubEvent.Message))
|
||||||
|
}
|
||||||
|
|
||||||
if !s.sentCtcpVersion {
|
if !s.recievedFirstServerMessage {
|
||||||
// Abuse this first username in order to also send a CTCP version request
|
s.hubSecNick = hubEvent.Nick // Replace with the hub's real Hub-Security nick, although we shouldn't need it again
|
||||||
s.reply(rplMsg, hubEvent.Nick+"!"+hubEvent.Nick+"@"+hubEvent.Nick, BLESSED_CHANNEL, "\x01VERSION\x01")
|
|
||||||
s.sentCtcpVersion = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case libnmdc.EVENT_SYSTEM_MESSAGE_FROM_CONN, libnmdc.EVENT_SYSTEM_MESSAGE_FROM_HUB:
|
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.reply(rplWelcome)
|
||||||
s.clientState = CSRegistered
|
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 {
|
if s.autojoin {
|
||||||
s.handleCommand("JOIN", []string{BLESSED_CHANNEL})
|
s.handleCommand("JOIN", []string{BLESSED_CHANNEL})
|
||||||
}
|
}
|
||||||
@ -382,101 +390,6 @@ func (s *Server) handleRegisteredCommand(command string, args []string) {
|
|||||||
}
|
}
|
||||||
s.reply(rplListEnd)
|
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":
|
case "PRIVMSG", "NOTICE":
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
s.reply(errMoreArgs)
|
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":
|
case "QUIT":
|
||||||
s.DisconnectClient()
|
s.DisconnectClient()
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ const (
|
|||||||
NICKS_PER_PROTOMSG = 128 // Max number of nicks to send per message
|
NICKS_PER_PROTOMSG = 128 // Max number of nicks to send per message
|
||||||
CLIENT_READ_BUFFSIZE = 512
|
CLIENT_READ_BUFFSIZE = 512
|
||||||
CLIENT_KEEPALIVE_EVERY = 60 // should be longer than the client's one, hexchat is 30s
|
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
|
type replyCode int
|
||||||
|
Loading…
Reference in New Issue
Block a user