Add downstream support for cap-notify

This commit is contained in:
Simon Ser 2020-04-29 19:07:15 +02:00
parent 70131f5b31
commit 394f2853ad
No known key found for this signature in database
GPG key ID: 0FDE7BE0E88F5E48

View file

@ -50,6 +50,17 @@ var errAuthFailed = ircError{&irc.Message{
Params: []string{"*", "Invalid username or password"}, Params: []string{"*", "Invalid username or password"},
}} }}
// permanentDownstreamCaps is the list of always-supported downstream
// capabilities.
var permanentDownstreamCaps = map[string]string{
"batch": "",
"cap-notify": "",
"echo-message": "",
"message-tags": "",
"sasl": "PLAIN",
"server-time": "",
}
type downstreamConn struct { type downstreamConn struct {
conn conn
@ -68,6 +79,7 @@ type downstreamConn struct {
negociatingCaps bool negociatingCaps bool
capVersion int capVersion int
supportedCaps map[string]string
caps map[string]bool caps map[string]bool
saslServer sasl.Server saslServer sasl.Server
@ -78,12 +90,16 @@ func newDownstreamConn(srv *Server, netConn net.Conn, id uint64) *downstreamConn
dc := &downstreamConn{ dc := &downstreamConn{
conn: *newConn(srv, netConn, logger), conn: *newConn(srv, netConn, logger),
id: id, id: id,
supportedCaps: make(map[string]string),
caps: make(map[string]bool), caps: make(map[string]bool),
} }
dc.hostname = netConn.RemoteAddr().String() dc.hostname = netConn.RemoteAddr().String()
if host, _, err := net.SplitHostPort(dc.hostname); err == nil { if host, _, err := net.SplitHostPort(dc.hostname); err == nil {
dc.hostname = host dc.hostname = host
} }
for k, v := range permanentDownstreamCaps {
dc.supportedCaps[k] = v
}
return dc return dc
} }
@ -439,12 +455,13 @@ func (dc *downstreamConn) handleCapCommand(cmd string, args []string) error {
} }
} }
caps := []string{"message-tags", "server-time", "echo-message", "batch"} caps := make([]string, 0, len(dc.supportedCaps))
for k, v := range dc.supportedCaps {
if dc.capVersion >= 302 { if dc.capVersion >= 302 && v != "" {
caps = append(caps, "sasl=PLAIN") caps = append(caps, k + "=" + v)
} else { } else {
caps = append(caps, "sasl") caps = append(caps, k)
}
} }
// TODO: multi-line replies // TODO: multi-line replies
@ -454,6 +471,11 @@ func (dc *downstreamConn) handleCapCommand(cmd string, args []string) error {
Params: []string{replyTo, "LS", strings.Join(caps, " ")}, Params: []string{replyTo, "LS", strings.Join(caps, " ")},
}) })
if dc.capVersion >= 302 {
// CAP version 302 implicitly enables cap-notify
dc.caps["cap-notify"] = true
}
if !dc.registered { if !dc.registered {
dc.negociatingCaps = true dc.negociatingCaps = true
} }
@ -477,6 +499,7 @@ func (dc *downstreamConn) handleCapCommand(cmd string, args []string) error {
}} }}
} }
// TODO: atomically ack/nak the whole capability set
caps := strings.Fields(args[0]) caps := strings.Fields(args[0])
ack := true ack := true
for _, name := range caps { for _, name := range caps {
@ -486,17 +509,23 @@ func (dc *downstreamConn) handleCapCommand(cmd string, args []string) error {
name = strings.TrimPrefix(name, "-") name = strings.TrimPrefix(name, "-")
} }
enabled := dc.caps[name] if enable == dc.caps[name] {
if enable == enabled {
continue continue
} }
switch name { _, ok := dc.supportedCaps[name]
case "sasl", "message-tags", "server-time", "echo-message", "batch": if !ok {
dc.caps[name] = enable
default:
ack = false ack = false
break
} }
if name == "cap-notify" && dc.capVersion >= 302 && !enable {
// cap-notify cannot be disabled with CAP version 302
ack = false
break
}
dc.caps[name] = enable
} }
reply := "NAK" reply := "NAK"
@ -519,6 +548,53 @@ func (dc *downstreamConn) handleCapCommand(cmd string, args []string) error {
return nil return nil
} }
func (dc *downstreamConn) setSupportedCap(name, value string) {
prevValue, hasPrev := dc.supportedCaps[name]
changed := !hasPrev || prevValue != value
dc.supportedCaps[name] = value
if !dc.caps["cap-notify"] || !changed {
return
}
replyTo := dc.nick
if !dc.registered {
replyTo = "*"
}
cap := name
if value != "" && dc.capVersion >= 302 {
cap = name + "=" + value
}
dc.SendMessage(&irc.Message{
Prefix: dc.srv.prefix(),
Command: "CAP",
Params: []string{replyTo, "NEW", cap},
})
}
func (dc *downstreamConn) unsetSupportedCap(name string) {
_, hasPrev := dc.supportedCaps[name]
delete(dc.supportedCaps, name)
delete(dc.caps, name)
if !dc.caps["cap-notify"] || !hasPrev {
return
}
replyTo := dc.nick
if !dc.registered {
replyTo = "*"
}
dc.SendMessage(&irc.Message{
Prefix: dc.srv.prefix(),
Command: "CAP",
Params: []string{replyTo, "DEL", name},
})
}
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)