You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

sasl.go 2.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. package ircutils
  2. import (
  3. "encoding/base64"
  4. "errors"
  5. "strings"
  6. )
  7. var (
  8. ErrSASLLimitExceeded = errors.New("SASL total response size exceeded configured limit")
  9. ErrSASLTooLong = errors.New("SASL response chunk exceeded 400-byte limit")
  10. )
  11. // EncodeSASLResponse encodes a raw SASL response as parameters to successive
  12. // AUTHENTICATE commands, as described in the IRCv3 SASL specification.
  13. func EncodeSASLResponse(raw []byte) (result []string) {
  14. // https://ircv3.net/specs/extensions/sasl-3.1#the-authenticate-command
  15. // "The response is encoded in Base64 (RFC 4648), then split to 400-byte chunks,
  16. // and each chunk is sent as a separate AUTHENTICATE command. Empty (zero-length)
  17. // responses are sent as AUTHENTICATE +. If the last chunk was exactly 400 bytes
  18. // long, it must also be followed by AUTHENTICATE + to signal end of response."
  19. if len(raw) == 0 {
  20. return []string{"+"}
  21. }
  22. response := base64.StdEncoding.EncodeToString(raw)
  23. lastLen := 0
  24. for len(response) > 0 {
  25. // TODO once we require go 1.21, this can be: lastLen = min(len(response), 400)
  26. lastLen = len(response)
  27. if lastLen > 400 {
  28. lastLen = 400
  29. }
  30. result = append(result, response[:lastLen])
  31. response = response[lastLen:]
  32. }
  33. if lastLen == 400 {
  34. result = append(result, "+")
  35. }
  36. return result
  37. }
  38. // SASLBuffer handles buffering and decoding SASL responses sent as parameters
  39. // to AUTHENTICATE commands, as described in the IRCv3 SASL specification.
  40. // Do not copy a SASLBuffer after first use.
  41. type SASLBuffer struct {
  42. maxLength int
  43. buffer strings.Builder
  44. }
  45. // NewSASLBuffer returns a new SASLBuffer. maxLength is the maximum amount of
  46. // base64'ed data to buffer (0 for no limit).
  47. func NewSASLBuffer(maxLength int) *SASLBuffer {
  48. result := new(SASLBuffer)
  49. result.Initialize(maxLength)
  50. return result
  51. }
  52. // Initialize initializes a SASLBuffer in place.
  53. func (b *SASLBuffer) Initialize(maxLength int) {
  54. b.maxLength = maxLength
  55. }
  56. // Add processes an additional SASL response chunk sent via AUTHENTICATE.
  57. // If the response is complete, it resets the buffer and returns the decoded
  58. // response along with any decoding or protocol errors detected.
  59. func (b *SASLBuffer) Add(value string) (done bool, output []byte, err error) {
  60. if value == "+" {
  61. output, err = b.getAndReset()
  62. return true, output, err
  63. }
  64. if len(value) > 400 {
  65. b.buffer.Reset()
  66. return true, nil, ErrSASLTooLong
  67. }
  68. if b.maxLength != 0 && (b.buffer.Len()+len(value)) > b.maxLength {
  69. b.buffer.Reset()
  70. return true, nil, ErrSASLLimitExceeded
  71. }
  72. b.buffer.WriteString(value)
  73. if len(value) < 400 {
  74. output, err = b.getAndReset()
  75. return true, output, err
  76. } else {
  77. // 400 bytes, wait for continuation line or +
  78. return false, nil, nil
  79. }
  80. }
  81. // Clear resets the buffer state.
  82. func (b *SASLBuffer) Clear() {
  83. b.buffer.Reset()
  84. }
  85. func (b *SASLBuffer) getAndReset() (output []byte, err error) {
  86. output, err = base64.StdEncoding.DecodeString(b.buffer.String())
  87. b.buffer.Reset()
  88. return
  89. }