1
0
Fork 0
mirror of https://github.com/fluffle/goirc synced 2025-07-05 21:09:24 +00:00

Add SASL authentication support

This hacks together support for IRCv3.1 SASL. Currently only SASL PLAIN
is supported, but it's implemented in a way that adding support for
other types should not require too many changes to the current code.
This commit is contained in:
Taavi Väänänen 2022-10-30 12:01:33 +02:00 committed by Alex Bee
parent bbbcc9aa5b
commit e64b5d47c3
6 changed files with 234 additions and 23 deletions

View file

@ -9,19 +9,27 @@ import (
"sync"
"time"
"encoding/base64"
"github.com/fluffle/goirc/logging"
)
// saslCap is the IRCv3 capability used for SASL authentication.
const saslCap = "sasl"
// sets up the internal event handlers to do essential IRC protocol things
var intHandlers = map[string]HandlerFunc{
REGISTER: (*Conn).h_REGISTER,
"001": (*Conn).h_001,
"433": (*Conn).h_433,
CTCP: (*Conn).h_CTCP,
NICK: (*Conn).h_NICK,
PING: (*Conn).h_PING,
CAP: (*Conn).h_CAP,
"410": (*Conn).h_410,
REGISTER: (*Conn).h_REGISTER,
"001": (*Conn).h_001,
"433": (*Conn).h_433,
CTCP: (*Conn).h_CTCP,
NICK: (*Conn).h_NICK,
PING: (*Conn).h_PING,
CAP: (*Conn).h_CAP,
"410": (*Conn).h_410,
AUTHENTICATE: (*Conn).h_AUTHENTICATE,
"903": (*Conn).h_903,
"904": (*Conn).h_904,
"908": (*Conn).h_908,
}
// set up the ircv3 capabilities supported by this client which will be requested by default to the server.
@ -59,6 +67,11 @@ func (conn *Conn) getRequestCapabilities() *capSet {
// add capabilites supported by the client
s.Add(defaultCaps...)
if conn.cfg.Sasl != nil {
// add the SASL cap if enabled
s.Add(saslCap)
}
// add capabilites requested by the user
s.Add(conn.cfg.Capabilites...)
@ -79,10 +92,31 @@ func (conn *Conn) negotiateCapabilities(supportedCaps []string) {
}
func (conn *Conn) handleCapAck(caps []string) {
gotSasl := false
for _, cap := range caps {
conn.currCaps.Add(cap)
if conn.cfg.Sasl != nil && cap == saslCap {
mech, ir, err := conn.cfg.Sasl.Start()
if err != nil {
logging.Warn("SASL authentication failed: %v", err)
continue
}
// TODO: when IRC 3.2 capability negotiation is supported, ensure the
// capability value is used to match the chosen mechanism
gotSasl = true
conn.saslRemainingData = ir
conn.Authenticate(mech)
}
}
if !gotSasl {
conn.Cap(CAP_END)
}
conn.Cap(CAP_END)
}
func (conn *Conn) handleCapNak(caps []string) {
@ -181,6 +215,60 @@ func (conn *Conn) h_CAP(line *Line) {
}
}
// Handler for SASL authentication
func (conn *Conn) h_AUTHENTICATE(line *Line) {
if conn.cfg.Sasl == nil {
return
}
if conn.saslRemainingData != nil {
data := "+" // plus sign representing empty data
if len(conn.saslRemainingData) > 0 {
data = base64.StdEncoding.EncodeToString(conn.saslRemainingData)
}
// TODO: batch data into chunks of 400 bytes per the spec
conn.Authenticate(data)
conn.saslRemainingData = nil
return
}
// TODO: handle data over 400 bytes long (which will be chunked into multiple messages per the spec)
challenge, err := base64.StdEncoding.DecodeString(line.Args[0])
if err != nil {
logging.Error("Failed to decode SASL challenge: %v", err)
return
}
response, err := conn.cfg.Sasl.Next(challenge)
if err != nil {
logging.Error("Failed to generate response for SASL challenge: %v", err)
return
}
// TODO: batch data into chunks of 400 bytes per the spec
data := base64.StdEncoding.EncodeToString(response)
conn.Authenticate(data)
}
// Handler for RPL_SASLSUCCESS.
func (conn *Conn) h_903(line *Line) {
conn.Cap(CAP_END)
}
// Handler for RPL_SASLFAILURE.
func (conn *Conn) h_904(line *Line) {
logging.Warn("SASL authentication failed")
conn.Cap(CAP_END)
}
// Handler for RPL_SASLMECHS.
func (conn *Conn) h_908(line *Line) {
logging.Warn("SASL mechanism not supported, supported mechanisms are: %v", line.Args[1])
conn.Cap(CAP_END)
}
// Handler to trigger a CONNECTED event on receipt of numeric 001
// :<server> 001 <nick> :Welcome message <nick>!<user>@<host>
func (conn *Conn) h_001(line *Line) {