155 lines
3.2 KiB
Go
155 lines
3.2 KiB
Go
package hbot
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
type ircCaps struct {
|
|
saslOn bool
|
|
saslUser string
|
|
saslPass string
|
|
caps []string
|
|
capsEnabled map[string]bool
|
|
mu sync.Mutex
|
|
done bool
|
|
}
|
|
|
|
func (c *ircCaps) saslEnable() {
|
|
c.mu.Lock()
|
|
c.saslOn = true
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
func (c *ircCaps) saslCreds(user, pass string) {
|
|
c.mu.Lock()
|
|
c.saslUser = user
|
|
c.saslPass = pass
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
func (c *ircCaps) reset() {
|
|
c.mu.Lock()
|
|
c.saslOn = false
|
|
c.done = false
|
|
c.caps = []string{}
|
|
c.capsEnabled = make(map[string]bool)
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
func (c *ircCaps) saslAuth(m *Message) bool {
|
|
return m.Command == "AUTHENTICATE" && m.Param(0) == "+"
|
|
}
|
|
|
|
func (c *ircCaps) capLS(m *Message) bool {
|
|
return m.Command == "CAP" && m.Param(1) == "LS"
|
|
}
|
|
|
|
func (c *ircCaps) capACK(m *Message) bool {
|
|
return m.Command == "CAP" && m.Param(1) == "ACK"
|
|
}
|
|
|
|
func (c *ircCaps) Handle(bot *Bot, m *Message) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
if c.done {
|
|
return
|
|
}
|
|
|
|
if c.capLS(m) {
|
|
for _, cap := range strings.Split(m.Trailing(), " ") {
|
|
if _, ok := allowedCAPs[cap]; ok {
|
|
c.capsEnabled[cap] = true
|
|
c.caps = append(c.caps, cap)
|
|
} else {
|
|
c.capsEnabled[cap] = false
|
|
}
|
|
}
|
|
bot.Send("CAP REQ :" + strings.Join(c.caps, " "))
|
|
}
|
|
|
|
if c.capACK(m) {
|
|
if c.saslOn && strings.Contains(m.Trailing(), "sasl") {
|
|
bot.Send("AUTHENTICATE PLAIN")
|
|
} else {
|
|
if c.saslOn {
|
|
bot.config.Logger.Errorf("SASL is not supported, despite request")
|
|
}
|
|
bot.Send("CAP END")
|
|
c.done = true
|
|
}
|
|
}
|
|
|
|
if c.saslAuth(m) {
|
|
out := bytes.Join([][]byte{[]byte(c.saslUser), []byte(c.saslUser), []byte(c.saslPass)}, []byte{0})
|
|
encpass := base64.StdEncoding.EncodeToString(out)
|
|
bot.Send("AUTHENTICATE " + encpass)
|
|
bot.Send("CAP END")
|
|
c.done = true
|
|
}
|
|
}
|
|
|
|
// Capabilities we can deal with
|
|
// without doing crazy things in the library
|
|
var allowedCAPs = map[string]struct{}{
|
|
CapAccountNotify: {},
|
|
CapAwayNotify: {},
|
|
CapExtendedJoin: {},
|
|
CapSASL: {},
|
|
CapChghost: {},
|
|
CapInviteNotify: {},
|
|
CapMultiPrefix: {},
|
|
CapCapNotify: {},
|
|
CapSetName: {},
|
|
CapServerTime: {},
|
|
CapAccountTag: {},
|
|
CapMessageTags: {},
|
|
}
|
|
|
|
// CapAccountNotify is account-notify CAP
|
|
const CapAccountNotify = "account-notify"
|
|
|
|
// CapAwayNotify is away-notify CAP
|
|
const CapAwayNotify = "away-notify"
|
|
|
|
// CapExtendedJoin is extended-join CAP
|
|
const CapExtendedJoin = "extended-join"
|
|
|
|
// CapSASL is SASL CAP
|
|
const CapSASL = "sasl"
|
|
|
|
// CapChghost is chghost CAP
|
|
const CapChghost = "chghost"
|
|
|
|
// CapInviteNotify is invite-notify CAP
|
|
const CapInviteNotify = "invite-notify"
|
|
|
|
// CapMultiPrefix is multi-prefix CAP
|
|
const CapMultiPrefix = "multi-prefix"
|
|
|
|
// CapUserhostInNames is userhost-in-names CAP
|
|
const CapUserhostInNames = "userhost-in-names"
|
|
|
|
// CapCapNotify is cap-notify CAP
|
|
const CapCapNotify = "cap-notify"
|
|
|
|
// CapIdentifyMsg is identify-msg CAP
|
|
const CapIdentifyMsg = "identify-msg"
|
|
|
|
// CapTLS is tls CAP
|
|
const CapTLS = "tls"
|
|
|
|
// CapSetName is setname CAP
|
|
const CapSetName = "setname"
|
|
|
|
// CapServerTime is server-time CAP
|
|
const CapServerTime = "server-time"
|
|
|
|
// CapAccountTag is account-tag CAP
|
|
const CapAccountTag = "account-tag"
|
|
|
|
// CapMessageTags is message-tags CAP
|
|
const CapMessageTags = "message-tags"
|