swap WHO for NAMES, ignore CAP requests, read NOTICEs, option to toggle autojoin, cosmetic fixes for hexchat
--HG-- branch : nmdc-ircfrontend
This commit is contained in:
parent
114823b0cc
commit
07a57777f7
16
TODO.txt
16
TODO.txt
@ -2,16 +2,26 @@
|
|||||||
PRE-RELEASE
|
PRE-RELEASE
|
||||||
===========
|
===========
|
||||||
|
|
||||||
- nick list
|
- hexchat: missing nick list
|
||||||
|
|
||||||
- part all nicks on server disconnection
|
- Atomic: fix crash on connection (even with autojoin disabled)
|
||||||
|
|
||||||
|
- yaaic: doesn't recognise the room join (even with autojoin disabled)
|
||||||
|
|
||||||
|
- andchat: WHO spam
|
||||||
|
|
||||||
|
- part all nicks upon upstream server disconnection
|
||||||
|
|
||||||
- test the current password auth
|
- test the current password auth
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
WISHLIST
|
WISHLIST
|
||||||
========
|
========
|
||||||
|
|
||||||
|
- send client keepalives (PING) /and/ ensure we get a reply (PONG)
|
||||||
|
|
||||||
- client version sync (CTCP VERSION)
|
- client version sync (CTCP VERSION)
|
||||||
|
|
||||||
- client descriptions (CTCP USERINFO)
|
- client descriptions (CTCP USERINFO)
|
||||||
@ -34,6 +44,8 @@ WISHLIST
|
|||||||
|
|
||||||
- threadsafe access to libnmdc hub options
|
- threadsafe access to libnmdc hub options
|
||||||
|
|
||||||
|
- do we need to send the same payload in our PONG response?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
5
main.go
5
main.go
@ -31,9 +31,13 @@ func main() {
|
|||||||
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")
|
||||||
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")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
log.Printf("Listening on '%s'...", *ircAddress)
|
log.Printf("Listening on '%s'...", *ircAddress)
|
||||||
|
if *autojoin {
|
||||||
|
log.Printf("Clients will be automatically joined to the channel.")
|
||||||
|
}
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", *ircAddress)
|
listener, err := net.Listen("tcp", *ircAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -51,6 +55,7 @@ func main() {
|
|||||||
// Spin up a worker for the new user.
|
// Spin up a worker for the new user.
|
||||||
server := NewServer(*serverName, libnmdc.HubAddress(*dcAddress), conn)
|
server := NewServer(*serverName, libnmdc.HubAddress(*dcAddress), conn)
|
||||||
server.verbose = *verbose
|
server.verbose = *verbose
|
||||||
|
server.autojoin = *autojoin
|
||||||
go server.RunWorker()
|
go server.RunWorker()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
111
server.go
111
server.go
@ -29,18 +29,27 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ClientState int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CSUnregistered ClientState = iota
|
||||||
|
CSRegistered
|
||||||
|
CSJoined
|
||||||
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
name string
|
name string
|
||||||
motd string
|
motd string
|
||||||
|
|
||||||
clientConn net.Conn
|
clientConn net.Conn
|
||||||
clientRegistered bool
|
clientState ClientState
|
||||||
|
|
||||||
upstreamLauncher libnmdc.HubConnectionOptions
|
upstreamLauncher libnmdc.HubConnectionOptions
|
||||||
upstreamCloser chan struct{}
|
upstreamCloser chan struct{}
|
||||||
upstream *libnmdc.HubConnection
|
upstream *libnmdc.HubConnection
|
||||||
|
|
||||||
verbose bool
|
verbose bool
|
||||||
|
autojoin bool
|
||||||
|
|
||||||
lastMessage string // FIXME racey
|
lastMessage string // FIXME racey
|
||||||
}
|
}
|
||||||
@ -52,7 +61,8 @@ func NewServer(name string, upstream libnmdc.HubAddress, conn net.Conn) *Server
|
|||||||
return &Server{
|
return &Server{
|
||||||
name: name,
|
name: name,
|
||||||
clientConn: conn,
|
clientConn: conn,
|
||||||
motd: "Connected to " + name,
|
clientState: CSUnregistered,
|
||||||
|
motd: "Connected to " + name + ". You /must/ join " + BLESSED_CHANNEL + " to continue.",
|
||||||
upstreamLauncher: libnmdc.HubConnectionOptions{
|
upstreamLauncher: libnmdc.HubConnectionOptions{
|
||||||
Address: upstream,
|
Address: upstream,
|
||||||
Self: *self,
|
Self: *self,
|
||||||
@ -120,13 +130,12 @@ func (s *Server) RunWorker() {
|
|||||||
s.verboseln("Broken loop.")
|
s.verboseln("Broken loop.")
|
||||||
|
|
||||||
// Cleanup upstream
|
// Cleanup upstream
|
||||||
if s.clientRegistered {
|
if s.upstream != nil {
|
||||||
s.upstreamCloser <- struct{}{} // always safe to do this /once/
|
s.upstreamCloser <- struct{}{} // always safe to do this /once/
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up ourselves
|
// Clean up ourselves
|
||||||
s.DisconnectClient() // if not already done
|
s.DisconnectClient() // if not already done
|
||||||
s.clientRegistered = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) upstreamWorker() {
|
func (s *Server) upstreamWorker() {
|
||||||
@ -158,7 +167,7 @@ func (s *Server) upstreamWorker() {
|
|||||||
s.sendChannelTopic(hubEvent.Nick)
|
s.sendChannelTopic(hubEvent.Nick)
|
||||||
|
|
||||||
case libnmdc.EVENT_PRIVATE:
|
case libnmdc.EVENT_PRIVATE:
|
||||||
s.reply(rplMsg, s.upstreamLauncher.Self.Nick, hubEvent.Nick, hubEvent.Message)
|
s.reply(rplMsg, hubEvent.Nick, s.upstreamLauncher.Self.Nick, hubEvent.Message)
|
||||||
|
|
||||||
case libnmdc.EVENT_PUBLIC:
|
case libnmdc.EVENT_PUBLIC:
|
||||||
if hubEvent.Nick == s.upstreamLauncher.Self.Nick && hubEvent.Message == s.lastMessage {
|
if hubEvent.Nick == s.upstreamLauncher.Self.Nick && hubEvent.Message == s.lastMessage {
|
||||||
@ -168,6 +177,7 @@ func (s *Server) upstreamWorker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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:
|
||||||
|
// FIXME blank names work well in hexchat, but not in yaaic
|
||||||
s.reply(rplMsg, "", BLESSED_CHANNEL, hubEvent.Message)
|
s.reply(rplMsg, "", BLESSED_CHANNEL, hubEvent.Message)
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -183,12 +193,33 @@ func (s *Server) handleCommand(command string, args []string) {
|
|||||||
case "PING":
|
case "PING":
|
||||||
s.reply(rplPong)
|
s.reply(rplPong)
|
||||||
|
|
||||||
|
case "PONG":
|
||||||
|
// do nothing
|
||||||
|
|
||||||
case "INFO":
|
case "INFO":
|
||||||
s.reply(rplInfo, APP_DESCRIPTION)
|
s.reply(rplInfo, APP_DESCRIPTION)
|
||||||
|
|
||||||
case "VERSION":
|
case "VERSION":
|
||||||
s.reply(rplVersion, VERSION)
|
s.reply(rplVersion, VERSION)
|
||||||
|
|
||||||
|
case "MOTD":
|
||||||
|
s.sendMOTD(s.motd)
|
||||||
|
|
||||||
|
case "CAP":
|
||||||
|
return
|
||||||
|
/*
|
||||||
|
if len(args) < 1 {
|
||||||
|
s.reply(errMoreArgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if args[0] == "LS" {
|
||||||
|
s.writeClient("CAP * LS :nmdc-ircfrontend") // no special IRCv3 capabilities available
|
||||||
|
} else {
|
||||||
|
s.writeClient(fmt.Sprintf(":%s 410 * %s :Invalid CAP command", s.name, args[0]))
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
case "PASS":
|
case "PASS":
|
||||||
// RFC2812 registration. Stash the password for later
|
// RFC2812 registration. Stash the password for later
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
@ -215,7 +246,7 @@ func (s *Server) handleCommand(command string, args []string) {
|
|||||||
// This command sets altname, realname, ... none of which we use
|
// This command sets altname, realname, ... none of which we use
|
||||||
// It's the final step in a PASS/NICK/USER login handshake.
|
// It's the final step in a PASS/NICK/USER login handshake.
|
||||||
|
|
||||||
if s.clientRegistered == true {
|
if s.clientState != CSUnregistered {
|
||||||
s.reply(rplKill, "You're already registered.", "")
|
s.reply(rplKill, "You're already registered.", "")
|
||||||
s.DisconnectClient()
|
s.DisconnectClient()
|
||||||
return
|
return
|
||||||
@ -228,18 +259,12 @@ func (s *Server) handleCommand(command string, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.reply(rplWelcome)
|
s.reply(rplWelcome)
|
||||||
s.clientRegistered = true
|
s.clientState = CSRegistered
|
||||||
|
|
||||||
// Tell the user that they themselves joined the chat channel
|
// TODO tell the client that they /must/ join #chat
|
||||||
s.reply(rplJoin, s.upstreamLauncher.Self.Nick, BLESSED_CHANNEL)
|
if s.autojoin {
|
||||||
|
s.handleCommand("JOIN", []string{BLESSED_CHANNEL})
|
||||||
// Send (initially just us) nicklist for the chat channel
|
}
|
||||||
s.reply(rplNames, BLESSED_CHANNEL, s.upstreamLauncher.Self.Nick)
|
|
||||||
s.reply(rplEndOfNames, BLESSED_CHANNEL)
|
|
||||||
|
|
||||||
// Spawn upstream connection
|
|
||||||
s.upstream = s.upstreamLauncher.Connect()
|
|
||||||
go s.upstreamWorker()
|
|
||||||
|
|
||||||
// Send a CTCP VERSION request to the client. If the IRC client can
|
// Send a CTCP VERSION request to the client. If the IRC client can
|
||||||
// supply a client version string, we can replace our tag with it,
|
// supply a client version string, we can replace our tag with it,
|
||||||
@ -252,7 +277,7 @@ func (s *Server) handleCommand(command string, args []string) {
|
|||||||
|
|
||||||
func (s *Server) handleRegisteredCommand(command string, args []string) {
|
func (s *Server) handleRegisteredCommand(command string, args []string) {
|
||||||
|
|
||||||
if s.clientRegistered == false {
|
if s.clientState == CSUnregistered {
|
||||||
s.reply(errNotReg)
|
s.reply(errNotReg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -266,9 +291,30 @@ func (s *Server) handleRegisteredCommand(command string, args []string) {
|
|||||||
|
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
case BLESSED_CHANNEL:
|
case BLESSED_CHANNEL:
|
||||||
// Ignore, but they're already there
|
if s.clientState != CSJoined {
|
||||||
|
// Join for the first time
|
||||||
|
s.clientState = CSJoined
|
||||||
|
|
||||||
|
// Acknowledge
|
||||||
|
s.reply(rplJoin, s.upstreamLauncher.Self.Nick, 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":
|
case "0":
|
||||||
// Ignore, Intend to quit all channels but we don't allow that
|
// Quitting all channels? Drop client
|
||||||
|
s.reply(rplKill, "Bye.", "")
|
||||||
|
s.DisconnectClient()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
s.reply(rplKill, "There is only '"+BLESSED_CHANNEL+"'.", "")
|
s.reply(rplKill, "There is only '"+BLESSED_CHANNEL+"'.", "")
|
||||||
s.DisconnectClient()
|
s.DisconnectClient()
|
||||||
@ -283,9 +329,10 @@ func (s *Server) handleRegisteredCommand(command string, args []string) {
|
|||||||
if args[0] == BLESSED_CHANNEL {
|
if args[0] == BLESSED_CHANNEL {
|
||||||
// You can check out any time you like, but you can never leave
|
// You can check out any time you like, but you can never leave
|
||||||
s.reply(rplJoin, s.upstreamLauncher.Self.Nick, BLESSED_CHANNEL)
|
s.reply(rplJoin, s.upstreamLauncher.Self.Nick, BLESSED_CHANNEL)
|
||||||
|
s.sendNames()
|
||||||
}
|
}
|
||||||
|
|
||||||
case "PRIVMSG":
|
case "PRIVMSG", "NOTICE":
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
s.reply(errMoreArgs)
|
s.reply(errMoreArgs)
|
||||||
return
|
return
|
||||||
@ -357,6 +404,10 @@ func (s *Server) handleRegisteredCommand(command string, args []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore what we're "supposed" to do
|
||||||
|
s.sendNames()
|
||||||
|
|
||||||
|
/*
|
||||||
if args[0] == BLESSED_CHANNEL {
|
if args[0] == BLESSED_CHANNEL {
|
||||||
// always include ourselves
|
// always include ourselves
|
||||||
s.reply(rplWho, s.upstreamLauncher.Self.Nick, args[0])
|
s.reply(rplWho, s.upstreamLauncher.Self.Nick, args[0])
|
||||||
@ -375,6 +426,7 @@ func (s *Server) handleRegisteredCommand(command string, args []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.reply(rplEndOfWho, args[0])
|
s.reply(rplEndOfWho, args[0])
|
||||||
|
*/
|
||||||
|
|
||||||
case "MODE":
|
case "MODE":
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
@ -407,10 +459,23 @@ func (s *Server) DisconnectClient() {
|
|||||||
s.clientConn.Close()
|
s.clientConn.Close()
|
||||||
}
|
}
|
||||||
s.clientConn = nil
|
s.clientConn = nil
|
||||||
|
s.clientState = CSUnregistered
|
||||||
|
|
||||||
// Readloop will stop, which kills the upstream connection too
|
// Readloop will stop, which kills the upstream connection too
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) sendNames() {
|
||||||
|
names := s.upstreamLauncher.Self.Nick
|
||||||
|
if s.upstream != nil {
|
||||||
|
for nick, _ := range s.upstream.Users {
|
||||||
|
names += " " + nick
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.reply(rplNames, BLESSED_CHANNEL, names)
|
||||||
|
s.reply(rplEndOfNames, BLESSED_CHANNEL)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) sendChannelTopic(topic string) {
|
func (s *Server) sendChannelTopic(topic string) {
|
||||||
if len(topic) > 0 {
|
if len(topic) > 0 {
|
||||||
s.reply(rplTopic, BLESSED_CHANNEL, s.upstream.HubName)
|
s.reply(rplTopic, BLESSED_CHANNEL, s.upstream.HubName)
|
||||||
@ -485,7 +550,7 @@ func (s *Server) reply(code replyCode, args ...string) {
|
|||||||
case rplMOTD:
|
case rplMOTD:
|
||||||
s.writeClient(fmt.Sprintf(":%s 372 %s :- %s", s.name, s.upstreamLauncher.Self.Nick, args[0]))
|
s.writeClient(fmt.Sprintf(":%s 372 %s :- %s", s.name, s.upstreamLauncher.Self.Nick, args[0]))
|
||||||
case rplEndOfMOTD:
|
case rplEndOfMOTD:
|
||||||
s.writeClient(fmt.Sprintf(":%s 376 %s :End of MOTD Command", s.name, s.upstreamLauncher.Self.Nick))
|
s.writeClient(fmt.Sprintf(":%s 376 %s :- End of MOTD", s.name, s.upstreamLauncher.Self.Nick))
|
||||||
case rplPong:
|
case rplPong:
|
||||||
s.writeClient(fmt.Sprintf(":%s PONG %s %s", s.name, s.upstreamLauncher.Self.Nick, s.name))
|
s.writeClient(fmt.Sprintf(":%s PONG %s %s", s.name, s.upstreamLauncher.Self.Nick, s.name))
|
||||||
case errMoreArgs:
|
case errMoreArgs:
|
||||||
@ -503,7 +568,7 @@ func (s *Server) reply(code replyCode, args ...string) {
|
|||||||
case errUnknownCommand:
|
case errUnknownCommand:
|
||||||
s.writeClient(fmt.Sprintf(":%s 421 %s %s :Unknown command", s.name, s.upstreamLauncher.Self.Nick, args[0]))
|
s.writeClient(fmt.Sprintf(":%s 421 %s %s :Unknown command", s.name, s.upstreamLauncher.Self.Nick, args[0]))
|
||||||
case errNotReg:
|
case errNotReg:
|
||||||
s.writeClient(fmt.Sprintf(":%s 451 :You have not registered", s.name))
|
s.writeClient(fmt.Sprintf(":%s 451 :- You have not registered", s.name))
|
||||||
case errPassword:
|
case errPassword:
|
||||||
s.writeClient(fmt.Sprintf(":%s 464 %s :Error, password incorrect", s.name, s.upstreamLauncher.Self.Nick))
|
s.writeClient(fmt.Sprintf(":%s 464 %s :Error, password incorrect", s.name, s.upstreamLauncher.Self.Nick))
|
||||||
case errNoPriv:
|
case errNoPriv:
|
||||||
|
Loading…
Reference in New Issue
Block a user