Add CAP support for downstream connections

This commit is contained in:
Simon Ser 2020-03-16 15:05:24 +01:00
parent 87684f7eab
commit af76c3868a
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
2 changed files with 124 additions and 14 deletions

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -71,6 +72,10 @@ type downstreamConn struct {
password string // empty after authentication password string // empty after authentication
network *network // can be nil network *network // can be nil
negociatingCaps bool
capVersion int
caps map[string]bool
lock sync.Mutex lock sync.Mutex
ourMessages map[*irc.Message]struct{} ourMessages map[*irc.Message]struct{}
} }
@ -84,6 +89,7 @@ func newDownstreamConn(srv *Server, netConn net.Conn) *downstreamConn {
outgoing: make(chan *irc.Message, 64), outgoing: make(chan *irc.Message, 64),
ringMessages: make(chan ringMessage), ringMessages: make(chan ringMessage),
closed: make(chan struct{}), closed: make(chan struct{}),
caps: make(map[string]bool),
ourMessages: make(map[*irc.Message]struct{}), ourMessages: make(map[*irc.Message]struct{}),
} }
@ -328,16 +334,119 @@ func (dc *downstreamConn) handleMessageUnregistered(msg *irc.Message) error {
if err := parseMessageParams(msg, &dc.password); err != nil { if err := parseMessageParams(msg, &dc.password); err != nil {
return err return err
} }
case "CAP":
var subCmd string
if err := parseMessageParams(msg, &subCmd); err != nil {
return err
}
subCmd = strings.ToUpper(subCmd)
if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
return err
}
default: default:
dc.logger.Printf("unhandled message: %v", msg) dc.logger.Printf("unhandled message: %v", msg)
return newUnknownCommandError(msg.Command) return newUnknownCommandError(msg.Command)
} }
if dc.rawUsername != "" && dc.nick != "" { if dc.rawUsername != "" && dc.nick != "" && !dc.negociatingCaps {
return dc.register() return dc.register()
} }
return nil return nil
} }
func (dc *downstreamConn) handleCapCommand(cmd string, args []string) error {
replyTo := dc.nick
if !dc.registered {
replyTo = "*"
}
switch cmd {
case "LS":
if len(args) > 0 {
var err error
if dc.capVersion, err = strconv.Atoi(args[0]); err != nil {
return err
}
}
var caps []string
/*if dc.capVersion >= 302 {
caps = append(caps, "sasl=PLAIN")
} else {
caps = append(caps, "sasl")
}*/
// TODO: multi-line replies
dc.SendMessage(&irc.Message{
Prefix: dc.srv.prefix(),
Command: "CAP",
Params: []string{replyTo, "LS", strings.Join(caps, " ")},
})
if !dc.registered {
dc.negociatingCaps = true
}
case "LIST":
var caps []string
for name := range dc.caps {
caps = append(caps, name)
}
// TODO: multi-line replies
dc.SendMessage(&irc.Message{
Prefix: dc.srv.prefix(),
Command: "CAP",
Params: []string{replyTo, "LIST", strings.Join(caps, " ")},
})
case "REQ":
if len(args) == 0 {
return ircError{&irc.Message{
Command: err_invalidcapcmd,
Params: []string{replyTo, cmd, "Missing argument in CAP REQ command"},
}}
}
caps := strings.Fields(args[0])
ack := true
for _, name := range caps {
name = strings.ToLower(name)
enable := !strings.HasPrefix(name, "-")
if !enable {
name = strings.TrimPrefix(name, "-")
}
enabled := dc.caps[name]
if enable == enabled {
continue
}
switch name {
/*case "sasl":
dc.caps[name] = enable*/
default:
ack = false
}
}
reply := "NAK"
if ack {
reply = "ACK"
}
dc.SendMessage(&irc.Message{
Prefix: dc.srv.prefix(),
Command: "CAP",
Params: []string{replyTo, reply, args[0]},
})
case "END":
dc.negociatingCaps = false
default:
return ircError{&irc.Message{
Command: err_invalidcapcmd,
Params: []string{replyTo, cmd, "Unknown CAP command"},
}}
}
return nil
}
func sanityCheckServer(addr string) error { func sanityCheckServer(addr string) error {
dialer := net.Dialer{Timeout: 30 * time.Second} dialer := net.Dialer{Timeout: 30 * time.Second}
conn, err := tls.DialWithDialer(&dialer, "tcp", addr, nil) conn, err := tls.DialWithDialer(&dialer, "tcp", addr, nil)

27
irc.go
View File

@ -8,19 +8,20 @@ import (
) )
const ( const (
rpl_statsping = "246" rpl_statsping = "246"
rpl_localusers = "265" rpl_localusers = "265"
rpl_globalusers = "266" rpl_globalusers = "266"
rpl_topicwhotime = "333" rpl_topicwhotime = "333"
rpl_loggedin = "900" err_invalidcapcmd = "410"
rpl_loggedout = "901" rpl_loggedin = "900"
err_nicklocked = "902" rpl_loggedout = "901"
rpl_saslsuccess = "903" err_nicklocked = "902"
err_saslfail = "904" rpl_saslsuccess = "903"
err_sasltoolong = "905" err_saslfail = "904"
err_saslaborted = "906" err_sasltoolong = "905"
err_saslalready = "907" err_saslaborted = "906"
rpl_saslmechs = "908" err_saslalready = "907"
rpl_saslmechs = "908"
) )
type modeSet string type modeSet string