From e3d97bb164e1d62265dad0a8ba913e82054153b8 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 18 Mar 2020 12:23:08 +0100 Subject: [PATCH] Add basic infrastructure for bouncer service --- downstream.go | 19 ++++++++--- go.mod | 1 + go.sum | 2 ++ service.go | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++ upstream.go | 17 +++++++++- 5 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 service.go diff --git a/downstream.go b/downstream.go index 81b1049..50b33ad 100644 --- a/downstream.go +++ b/downstream.go @@ -326,15 +326,21 @@ func (dc *downstreamConn) handleMessage(msg *irc.Message) error { func (dc *downstreamConn) handleMessageUnregistered(msg *irc.Message) error { switch msg.Command { case "NICK": - if err := parseMessageParams(msg, &dc.nick); err != nil { + var nick string + if err := parseMessageParams(msg, &nick); err != nil { return err } + if nick == serviceNick { + return ircError{&irc.Message{ + Command: irc.ERR_NICKNAMEINUSE, + Params: []string{dc.nick, nick, "Nickname reserved for bouncer service"}, + }} + } + dc.nick = nick case "USER": - var username string - if err := parseMessageParams(msg, &username, nil, nil, &dc.realname); err != nil { + if err := parseMessageParams(msg, &dc.rawUsername, nil, nil, &dc.realname); err != nil { return err } - dc.rawUsername = username case "PASS": if err := parseMessageParams(msg, &dc.password); err != nil { return err @@ -890,6 +896,11 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error { } for _, name := range strings.Split(targetsStr, ",") { + if name == serviceNick { + handleServicePRIVMSG(dc, text) + continue + } + uc, upstreamName, err := dc.unmarshalChannel(name) if err != nil { return err diff --git a/go.mod b/go.mod index 9d41c8c..3e78c2f 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.13 require ( github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/mattn/go-sqlite3 v2.0.3+incompatible golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect diff --git a/go.sum b/go.sum index 58ed7ef..20c35c4 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b h1:uhWtEWBHgop1rqEk2klKaxPAkVDCXexai6hSuRQ7Nvs= github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/service.go b/service.go new file mode 100644 index 0000000..35f0ae5 --- /dev/null +++ b/service.go @@ -0,0 +1,87 @@ +package soju + +import ( + "fmt" + "strings" + + "github.com/google/shlex" + "gopkg.in/irc.v3" +) + +const serviceNick = "BouncerServ" + +type serviceCommand struct { + usage string + desc string + handle func(dc *downstreamConn, params []string) error +} + +func sendServicePRIVMSG(dc *downstreamConn, text string) { + dc.SendMessage(&irc.Message{ + Prefix: &irc.Prefix{Name: serviceNick}, + Command: "PRIVMSG", + Params: []string{dc.nick, text}, + }) +} + +func handleServicePRIVMSG(dc *downstreamConn, text string) { + words, err := shlex.Split(text) + if err != nil { + sendServicePRIVMSG(dc, fmt.Sprintf("error: failed to parse command: %v", err)) + return + } + + var name string + var params []string + if len(words) > 0 { + name = strings.ToLower(words[0]) + params = words[1:] + } + + cmd, ok := serviceCommands[name] + if !ok { + sendServicePRIVMSG(dc, fmt.Sprintf(`error: unknown command %q (type "help" for a list of commands)`, name)) + return + } + + if err := cmd.handle(dc, params); err != nil { + sendServicePRIVMSG(dc, fmt.Sprintf("error: %v", err)) + } +} + +var serviceCommands map[string]serviceCommand + +func init() { + serviceCommands = map[string]serviceCommand{ + "help": { + usage: "[command]", + desc: "print help message", + handle: handleServiceHelp, + }, + } +} + +func handleServiceHelp(dc *downstreamConn, params []string) error { + if len(params) > 0 { + name := strings.ToLower(params[0]) + cmd, ok := serviceCommands[name] + if !ok { + return fmt.Errorf("unknown command %q", name) + } + + text := name + if cmd.usage != "" { + text += " " + cmd.usage + } + text += ": " + cmd.desc + + sendServicePRIVMSG(dc, text) + } else { + var l []string + for name := range serviceCommands { + l = append(l, name) + } + sendServicePRIVMSG(dc, "available commands: "+strings.Join(l, ", ")) + } + return nil +} diff --git a/upstream.go b/upstream.go index 83ff24b..087e932 100644 --- a/upstream.go +++ b/upstream.go @@ -558,9 +558,24 @@ func (uc *upstreamConn) handleMessage(msg *irc.Message) error { forwardChannel(dc, ch) }) case "PRIVMSG": - if err := parseMessageParams(msg, nil, nil); err != nil { + if msg.Prefix == nil { + return fmt.Errorf("expected a prefix") + } + + var nick string + if err := parseMessageParams(msg, &nick, nil); err != nil { return err } + + if msg.Prefix.Name == serviceNick { + uc.logger.Printf("skipping PRIVMSG from soju's service: %v", msg) + break + } + if nick == serviceNick { + uc.logger.Printf("skipping PRIVMSG to soju's service: %v", msg) + break + } + uc.ring.Produce(msg) case "INVITE": var nick string