verbose support, WHO support, global messages in blessed channel, prevent leaving blessed channel

--HG--
branch : nmdc-ircfrontend
This commit is contained in:
. 2016-05-07 13:55:29 +12:00
parent 2c84934ab8
commit 479fbf104b
4 changed files with 79 additions and 21 deletions

View File

@ -4,10 +4,10 @@ PRE-RELEASE
- nick list - nick list
- expose unprefixed system messages in chat channel
- part all nicks on server disconnection - part all nicks on server disconnection
- test the current password auth
WISHLIST WISHLIST
======== ========
@ -20,10 +20,19 @@ WISHLIST
- expose upstream op status - expose upstream op status
- support WHO, WHOIS, USERIP/KILL/KICK (if opped) - support WHO
- support WHOIS
- support USERIP/KILL/KICK for ops
- use CTCP chat to support irc-special characters in chat messages (colon, newline) - use CTCP chat to support irc-special characters in chat messages (colon, newline)
- respond to CAP LS 302 (hexchat) in some way that'll kick us into a modern protocol mode
- support SASL PLAIN authentication as well as just classic PASS
- threadsafe access to libnmdc hub options

11
main.go
View File

@ -25,14 +25,12 @@ import (
"net" "net"
) )
var (
ircAddress = flag.String("bind", ":6697", "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")
)
func main() { func main() {
ircAddress := flag.String("bind", ":6697", "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")
verbose := flag.Bool("verbose", false, "Display debugging information")
flag.Parse() flag.Parse()
log.Printf("Listening on '%s'...", *ircAddress) log.Printf("Listening on '%s'...", *ircAddress)
@ -52,6 +50,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
go server.RunWorker() go server.RunWorker()
} }
} }

View File

@ -40,6 +40,8 @@ type Server struct {
upstreamCloser chan struct{} upstreamCloser chan struct{}
upstream *libnmdc.HubConnection upstream *libnmdc.HubConnection
verbose bool
lastMessage string // FIXME racey lastMessage string // FIXME racey
} }
@ -59,6 +61,18 @@ func NewServer(name string, upstream libnmdc.HubAddress, conn net.Conn) *Server
} }
} }
func (s *Server) verboseln(line string) {
if s.verbose {
log.Println(line)
}
}
func (s *Server) verbosef(fmt string, args ...interface{}) {
if s.verbose {
log.Printf(fmt, args...)
}
}
func (s *Server) RunWorker() { func (s *Server) RunWorker() {
// Send the connection handshake. // Send the connection handshake.
@ -72,13 +86,12 @@ func (s *Server) RunWorker() {
buf := make([]byte, CLIENT_READ_BUFFSIZE) buf := make([]byte, CLIENT_READ_BUFFSIZE)
//s.clientConn.SetReadDeadline(time.Now().Add(5 * time.Second))
ln, err := s.clientConn.Read(buf) ln, err := s.clientConn.Read(buf)
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
break // abandon thread break // abandon thread
} }
//log.Println(err.Error()) s.verboseln(err.Error())
continue continue
} }
@ -104,7 +117,7 @@ func (s *Server) RunWorker() {
} }
} }
log.Println("Broken loop.") s.verboseln("Broken loop.")
// Cleanup upstream // Cleanup upstream
if s.clientRegistered { if s.clientRegistered {
@ -123,7 +136,7 @@ func (s *Server) upstreamWorker() {
select { select {
case <-s.upstreamCloser: case <-s.upstreamCloser:
// Abandon the upstream connection // Abandon the upstream connection
log.Println("Abandoning upstream connection...") s.verboseln("Abandoning upstream connection...")
s.upstream.Disconnect() s.upstream.Disconnect()
return return
@ -155,7 +168,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:
s.reply(rplMsg, BLESSED_CHANNEL, BLESSED_CHANNEL, hubEvent.Message) // experimental s.reply(rplMsg, "", BLESSED_CHANNEL, hubEvent.Message) // experimental
// s.sendClientGlobalMessage(hubEvent.Message) // s.sendClientGlobalMessage(hubEvent.Message)
} }
@ -165,6 +178,8 @@ func (s *Server) upstreamWorker() {
func (s *Server) handleCommand(command string, args []string) { func (s *Server) handleCommand(command string, args []string) {
s.verbosef(" >>> '%s' %v", command, args)
switch command { switch command {
case "PING": case "PING":
s.reply(rplPong) s.reply(rplPong)
@ -261,7 +276,15 @@ func (s *Server) handleRegisteredCommand(command string, args []string) {
} }
case "PART": 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 // You can check out any time you like, but you can never leave
s.reply(rplJoin, s.upstreamLauncher.Self.Nick, BLESSED_CHANNEL)
}
case "PRIVMSG": case "PRIVMSG":
if len(args) < 2 { if len(args) < 2 {
@ -325,18 +348,34 @@ func (s *Server) handleRegisteredCommand(command string, args []string) {
// Can't use this command. // Can't use this command.
s.reply(errPassword) s.reply(errPassword)
case "KILL": case "KILL", "KICK":
s.reply(errNoPriv) s.reply(errNoPriv)
return return
case "KICK": case "WHO":
if s.clientRegistered == false { if len(args) < 1 {
s.reply(errNotReg) s.reply(errMoreArgs)
return return
} }
s.reply(errNoPriv) if args[0] == BLESSED_CHANNEL {
return // always include ourselves
s.reply(rplWho, s.upstreamLauncher.Self.Nick)
for nick, _ := range s.upstream.Users {
if nick != s.upstreamLauncher.Self.Nick { // but don't repeat ourselves
s.reply(rplWho, nick)
}
}
} else {
// argument is a filter
for nick, _ := range s.upstream.Users {
if strings.Contains(nick, args[0]) {
s.reply(rplWho, nick)
}
}
}
s.reply(rplEndOfWho)
case "MODE": case "MODE":
if len(args) < 1 { if len(args) < 1 {
@ -409,10 +448,17 @@ func (s *Server) reply(code replyCode, args ...string) {
s.writeClient(fmt.Sprintf(":%s 332 %s %s :%s", s.name, s.upstreamLauncher.Self.Nick, args[0], args[1])) s.writeClient(fmt.Sprintf(":%s 332 %s %s :%s", s.name, s.upstreamLauncher.Self.Nick, args[0], args[1]))
case rplNoTopic: case rplNoTopic:
s.writeClient(fmt.Sprintf(":%s 331 %s %s :No topic is set", s.name, s.upstreamLauncher.Self.Nick, args[0])) s.writeClient(fmt.Sprintf(":%s 331 %s %s :No topic is set", s.name, s.upstreamLauncher.Self.Nick, args[0]))
case rplNames: case rplNames:
s.writeClient(fmt.Sprintf(":%s 353 %s = %s :%s", s.name, s.upstreamLauncher.Self.Nick, args[0], args[1])) s.writeClient(fmt.Sprintf(":%s 353 %s = %s :%s", s.name, s.upstreamLauncher.Self.Nick, args[0], args[1]))
case rplEndOfNames: case rplEndOfNames:
s.writeClient(fmt.Sprintf(":%s 366 %s %s :End of NAMES list", s.name, s.upstreamLauncher.Self.Nick, args[0])) s.writeClient(fmt.Sprintf(":%s 366 %s %s :End of NAMES list", s.name, s.upstreamLauncher.Self.Nick, args[0]))
case rplWho:
s.writeClient(fmt.Sprintf(":%s 352 %s %s %s %s %s %s H :0 %s", s.name, s.upstreamLauncher.Self.Nick, BLESSED_CHANNEL, args[0], args[0], s.name, args[0], args[0]))
case rplEndOfWho:
s.writeClient(fmt.Sprintf(":%s 315 :End of WHO list", s.name))
case rplNickChange: case rplNickChange:
s.writeClient(fmt.Sprintf(":%s NICK %s", args[0], args[1])) s.writeClient(fmt.Sprintf(":%s NICK %s", args[0], args[1]))
case rplKill: case rplKill:
@ -473,6 +519,8 @@ func (s *Server) writeClient(output string) {
return return
} }
s.verbosef(" <<< %s", output)
s.clientConn.SetWriteDeadline(time.Now().Add(time.Second * 30)) s.clientConn.SetWriteDeadline(time.Now().Add(time.Second * 30))
if _, err := fmt.Fprintf(s.clientConn, "%s\r\n", output); err != nil { if _, err := fmt.Fprintf(s.clientConn, "%s\r\n", output); err != nil {
s.DisconnectClient() s.DisconnectClient()

View File

@ -39,6 +39,8 @@ const (
rplNoTopic rplNoTopic
rplNames rplNames
rplEndOfNames rplEndOfNames
rplWho
rplEndOfWho
rplNickChange rplNickChange
rplKill rplKill
rplMsg rplMsg