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:
parent
bbbcc9aa5b
commit
e64b5d47c3
6 changed files with 234 additions and 23 deletions
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue