2024-02-14 00:58:32 +01:00
|
|
|
package ircutils
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/base64"
|
|
|
|
"errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrSASLLimitExceeded = errors.New("SASL total response size exceeded configured limit")
|
|
|
|
ErrSASLTooLong = errors.New("SASL response chunk exceeded 400-byte limit")
|
|
|
|
)
|
|
|
|
|
|
|
|
// EncodeSASLResponse encodes a raw SASL response as parameters to successive
|
|
|
|
// AUTHENTICATE commands, as described in the IRCv3 SASL specification.
|
|
|
|
func EncodeSASLResponse(raw []byte) (result []string) {
|
|
|
|
// https://ircv3.net/specs/extensions/sasl-3.1#the-authenticate-command
|
|
|
|
// "The response is encoded in Base64 (RFC 4648), then split to 400-byte chunks,
|
|
|
|
// and each chunk is sent as a separate AUTHENTICATE command. Empty (zero-length)
|
|
|
|
// responses are sent as AUTHENTICATE +. If the last chunk was exactly 400 bytes
|
|
|
|
// long, it must also be followed by AUTHENTICATE + to signal end of response."
|
|
|
|
|
|
|
|
if len(raw) == 0 {
|
|
|
|
return []string{"+"}
|
|
|
|
}
|
|
|
|
|
|
|
|
response := base64.StdEncoding.EncodeToString(raw)
|
2024-09-27 06:42:09 +02:00
|
|
|
result = make([]string, 0, (len(response)/400)+1)
|
2024-02-14 00:58:32 +01:00
|
|
|
lastLen := 0
|
|
|
|
for len(response) > 0 {
|
|
|
|
// TODO once we require go 1.21, this can be: lastLen = min(len(response), 400)
|
|
|
|
lastLen = len(response)
|
|
|
|
if lastLen > 400 {
|
|
|
|
lastLen = 400
|
|
|
|
}
|
|
|
|
result = append(result, response[:lastLen])
|
|
|
|
response = response[lastLen:]
|
|
|
|
}
|
|
|
|
|
|
|
|
if lastLen == 400 {
|
|
|
|
result = append(result, "+")
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
// SASLBuffer handles buffering and decoding SASL responses sent as parameters
|
|
|
|
// to AUTHENTICATE commands, as described in the IRCv3 SASL specification.
|
|
|
|
// Do not copy a SASLBuffer after first use.
|
|
|
|
type SASLBuffer struct {
|
|
|
|
maxLength int
|
2024-09-27 06:42:09 +02:00
|
|
|
buf []byte
|
2024-02-14 00:58:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewSASLBuffer returns a new SASLBuffer. maxLength is the maximum amount of
|
2024-09-27 06:42:09 +02:00
|
|
|
// data to buffer (0 for no limit).
|
2024-02-14 00:58:32 +01:00
|
|
|
func NewSASLBuffer(maxLength int) *SASLBuffer {
|
|
|
|
result := new(SASLBuffer)
|
|
|
|
result.Initialize(maxLength)
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize initializes a SASLBuffer in place.
|
|
|
|
func (b *SASLBuffer) Initialize(maxLength int) {
|
|
|
|
b.maxLength = maxLength
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add processes an additional SASL response chunk sent via AUTHENTICATE.
|
|
|
|
// If the response is complete, it resets the buffer and returns the decoded
|
|
|
|
// response along with any decoding or protocol errors detected.
|
|
|
|
func (b *SASLBuffer) Add(value string) (done bool, output []byte, err error) {
|
|
|
|
if value == "+" {
|
2024-09-27 06:42:09 +02:00
|
|
|
// total size is a multiple of 400 (possibly 0)
|
|
|
|
output = b.buf
|
|
|
|
b.Clear()
|
|
|
|
return true, output, nil
|
2024-02-14 00:58:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(value) > 400 {
|
2024-09-27 06:42:09 +02:00
|
|
|
b.Clear()
|
2024-02-14 00:58:32 +01:00
|
|
|
return true, nil, ErrSASLTooLong
|
|
|
|
}
|
|
|
|
|
2024-09-27 06:42:09 +02:00
|
|
|
curLen := len(b.buf)
|
|
|
|
chunkDecodedLen := base64.StdEncoding.DecodedLen(len(value))
|
|
|
|
if b.maxLength != 0 && (curLen+chunkDecodedLen) > b.maxLength {
|
|
|
|
b.Clear()
|
2024-02-14 00:58:32 +01:00
|
|
|
return true, nil, ErrSASLLimitExceeded
|
|
|
|
}
|
|
|
|
|
2024-09-27 06:42:09 +02:00
|
|
|
// "append-make pattern" as in the bytes.Buffer implementation:
|
|
|
|
b.buf = append(b.buf, make([]byte, chunkDecodedLen)...)
|
|
|
|
n, err := base64.StdEncoding.Decode(b.buf[curLen:], []byte(value))
|
|
|
|
b.buf = b.buf[0 : curLen+n]
|
|
|
|
if err != nil {
|
|
|
|
b.Clear()
|
|
|
|
return true, nil, err
|
|
|
|
}
|
2024-02-14 00:58:32 +01:00
|
|
|
if len(value) < 400 {
|
2024-09-27 06:42:09 +02:00
|
|
|
output = b.buf
|
|
|
|
b.Clear()
|
|
|
|
return true, output, nil
|
2024-02-14 00:58:32 +01:00
|
|
|
} else {
|
|
|
|
return false, nil, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear resets the buffer state.
|
|
|
|
func (b *SASLBuffer) Clear() {
|
2024-09-27 06:42:09 +02:00
|
|
|
// we can't reuse this buffer in general since we may have returned it
|
|
|
|
b.buf = nil
|
2024-02-14 00:58:32 +01:00
|
|
|
}
|