Pārlūkot izejas kodu

use SASL utilities from irc-go

pull/2122/head
Shivaram Lingamneni 3 mēnešus atpakaļ
vecāks
revīzija
9c71a96809
6 mainītis faili ar 142 papildinājumiem un 58 dzēšanām
  1. 1
    1
      go.mod
  2. 2
    0
      go.sum
  3. 11
    2
      irc/client.go
  4. 22
    52
      irc/handlers.go
  5. 105
    0
      vendor/github.com/ergochat/irc-go/ircutils/sasl.go
  6. 1
    3
      vendor/modules.txt

+ 1
- 1
go.mod Parādīt failu

8
 	github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
8
 	github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
9
 	github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1
9
 	github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1
10
 	github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881
10
 	github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881
11
-	github.com/ergochat/irc-go v0.4.0
11
+	github.com/ergochat/irc-go v0.5.0-rc1
12
 	github.com/go-sql-driver/mysql v1.7.0
12
 	github.com/go-sql-driver/mysql v1.7.0
13
 	github.com/go-test/deep v1.0.6 // indirect
13
 	github.com/go-test/deep v1.0.6 // indirect
14
 	github.com/gofrs/flock v0.8.1
14
 	github.com/gofrs/flock v0.8.1

+ 2
- 0
go.sum Parādīt failu

12
 github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881/go.mod h1:ASYJtQujNitna6cVHsNQTGrfWvMPJ5Sa2lZlmsH65uM=
12
 github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881/go.mod h1:ASYJtQujNitna6cVHsNQTGrfWvMPJ5Sa2lZlmsH65uM=
13
 github.com/ergochat/irc-go v0.4.0 h1:0YibCKfAAtwxQdNjLQd9xpIEPisLcJ45f8FNsMHAuZc=
13
 github.com/ergochat/irc-go v0.4.0 h1:0YibCKfAAtwxQdNjLQd9xpIEPisLcJ45f8FNsMHAuZc=
14
 github.com/ergochat/irc-go v0.4.0/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0=
14
 github.com/ergochat/irc-go v0.4.0/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0=
15
+github.com/ergochat/irc-go v0.5.0-rc1 h1:kFoIHExoNFQ2CV+iShAVna/H4xrXQB4t4jK5Sep2j9k=
16
+github.com/ergochat/irc-go v0.5.0-rc1/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0=
15
 github.com/ergochat/scram v1.0.2-ergo1 h1:2bYXiRFQH636pT0msOG39fmEYl4Eq+OuutcyDsCix/g=
17
 github.com/ergochat/scram v1.0.2-ergo1 h1:2bYXiRFQH636pT0msOG39fmEYl4Eq+OuutcyDsCix/g=
16
 github.com/ergochat/scram v1.0.2-ergo1/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
18
 github.com/ergochat/scram v1.0.2-ergo1/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
17
 github.com/ergochat/websocket v1.4.2-oragono1 h1:plMUunFBM6UoSCIYCKKclTdy/TkkHfUslhOfJQzfueM=
19
 github.com/ergochat/websocket v1.4.2-oragono1 h1:plMUunFBM6UoSCIYCKKclTdy/TkkHfUslhOfJQzfueM=

+ 11
- 2
irc/client.go Parādīt failu

21
 	"github.com/ergochat/irc-go/ircfmt"
21
 	"github.com/ergochat/irc-go/ircfmt"
22
 	"github.com/ergochat/irc-go/ircmsg"
22
 	"github.com/ergochat/irc-go/ircmsg"
23
 	"github.com/ergochat/irc-go/ircreader"
23
 	"github.com/ergochat/irc-go/ircreader"
24
+	"github.com/ergochat/irc-go/ircutils"
24
 	"github.com/xdg-go/scram"
25
 	"github.com/xdg-go/scram"
25
 
26
 
26
 	"github.com/ergochat/ergo/irc/caps"
27
 	"github.com/ergochat/ergo/irc/caps"
120
 
121
 
121
 type saslStatus struct {
122
 type saslStatus struct {
122
 	mechanism string
123
 	mechanism string
123
-	value     strings.Builder
124
+	value     ircutils.SASLBuffer
124
 	scramConv *scram.ServerConversation
125
 	scramConv *scram.ServerConversation
125
 	oauthConv *oauth2.OAuthBearerServer
126
 	oauthConv *oauth2.OAuthBearerServer
126
 }
127
 }
127
 
128
 
129
+func (s *saslStatus) Initialize() {
130
+	s.value.Initialize(saslMaxResponseLength)
131
+}
132
+
128
 func (s *saslStatus) Clear() {
133
 func (s *saslStatus) Clear() {
129
-	*s = saslStatus{}
134
+	s.mechanism = ""
135
+	s.value.Clear()
136
+	s.scramConv = nil
137
+	s.oauthConv = nil
130
 }
138
 }
131
 
139
 
132
 // what stage the client is at w.r.t. the PASS command:
140
 // what stage the client is at w.r.t. the PASS command:
364
 		isTor:      wConn.Tor,
372
 		isTor:      wConn.Tor,
365
 		hideSTS:    wConn.Tor || wConn.HideSTS,
373
 		hideSTS:    wConn.Tor || wConn.HideSTS,
366
 	}
374
 	}
375
+	session.sasl.Initialize()
367
 	client.sessions = []*Session{session}
376
 	client.sessions = []*Session{session}
368
 
377
 
369
 	session.resetFakelag()
378
 	session.resetFakelag()

+ 22
- 52
irc/handlers.go Parādīt failu

8
 
8
 
9
 import (
9
 import (
10
 	"bytes"
10
 	"bytes"
11
-	"encoding/base64"
12
 	"fmt"
11
 	"fmt"
13
 	"net"
12
 	"net"
14
 	"os"
13
 	"os"
180
 }
179
 }
181
 
180
 
182
 const (
181
 const (
183
-	saslMaxArgLength      = 400  // required by SASL spec
184
 	saslMaxResponseLength = 8192 // implementation-defined sanity check, long enough for bearer tokens
182
 	saslMaxResponseLength = 8192 // implementation-defined sanity check, long enough for bearer tokens
185
 )
183
 )
186
 
184
 
207
 		return false
205
 		return false
208
 	}
206
 	}
209
 
207
 
210
-	// start new sasl session
208
+	// start new sasl session: parameter is the authentication mechanism
211
 	if session.sasl.mechanism == "" {
209
 	if session.sasl.mechanism == "" {
212
 		throttled, remainingTime := client.loginThrottle.Touch()
210
 		throttled, remainingTime := client.loginThrottle.Touch()
213
 		if throttled {
211
 		if throttled {
246
 		return false
244
 		return false
247
 	}
245
 	}
248
 
246
 
249
-	// continue existing sasl session
250
-	rawData := msg.Params[0]
251
-
252
-	// https://ircv3.net/specs/extensions/sasl-3.1:
253
-	// "The response is encoded in Base64 (RFC 4648), then split to 400-byte chunks,
254
-	// and each chunk is sent as a separate AUTHENTICATE command."
255
-	if len(rawData) > saslMaxArgLength {
256
-		rb.Add(nil, server.name, ERR_SASLTOOLONG, details.nick, client.t("SASL message too long"))
257
-		session.sasl.Clear()
258
-		return false
259
-	} else if len(rawData) == saslMaxArgLength {
260
-		if session.sasl.value.Len() >= saslMaxResponseLength {
261
-			rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed: Passphrase too long"))
262
-			session.sasl.Clear()
263
-			return false
247
+	// continue existing sasl session: parameter is a message chunk
248
+	done, value, err := session.sasl.value.Add(msg.Params[0])
249
+	if err == nil {
250
+		if done {
251
+			// call actual handler
252
+			handler := EnabledSaslMechanisms[session.sasl.mechanism]
253
+			return handler(server, client, session, value, rb)
254
+		} else {
255
+			return false // wait for continuation line
264
 		}
256
 		}
265
-		session.sasl.value.WriteString(rawData)
266
-		return false
267
 	}
257
 	}
268
-	if rawData != "+" {
269
-		session.sasl.value.WriteString(rawData)
270
-	}
271
-
272
-	var data []byte
273
-	var err error
274
-	if session.sasl.value.Len() > 0 {
275
-		data, err = base64.StdEncoding.DecodeString(session.sasl.value.String())
276
-		session.sasl.value.Reset()
277
-		if err != nil {
278
-			rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed: Invalid b64 encoding"))
279
-			session.sasl.Clear()
280
-			return false
281
-		}
258
+	// else: error handling
259
+	switch err {
260
+	case ircutils.ErrSASLTooLong:
261
+		rb.Add(nil, server.name, ERR_SASLTOOLONG, details.nick, client.t("SASL message too long"))
262
+	case ircutils.ErrSASLLimitExceeded:
263
+		rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed: Passphrase too long"))
264
+	default:
265
+		rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed: Invalid b64 encoding"))
282
 	}
266
 	}
283
-
284
-	// call actual handler
285
-	handler := EnabledSaslMechanisms[session.sasl.mechanism]
286
-	return handler(server, client, session, data, rb)
267
+	session.sasl.Clear()
268
+	return false
287
 }
269
 }
288
 
270
 
289
 // AUTHENTICATE PLAIN
271
 // AUTHENTICATE PLAIN
495
 
477
 
496
 // helper to b64 a sasl response and chunk it into 400-byte lines
478
 // helper to b64 a sasl response and chunk it into 400-byte lines
497
 // as per https://ircv3.net/specs/extensions/sasl-3.1
479
 // as per https://ircv3.net/specs/extensions/sasl-3.1
498
-// TODO replace this with ircutils.EncodeSASLResponse
499
 func sendSASLChallenge(server *Server, rb *ResponseBuffer, challenge []byte) {
480
 func sendSASLChallenge(server *Server, rb *ResponseBuffer, challenge []byte) {
500
-	challengeStr := base64.StdEncoding.EncodeToString(challenge)
501
-	lastLen := 0
502
-	for len(challengeStr) > 0 {
503
-		end := saslMaxArgLength
504
-		if end > len(challengeStr) {
505
-			end = len(challengeStr)
506
-		}
507
-		lastLen = end
508
-		rb.Add(nil, server.name, "AUTHENTICATE", challengeStr[:end])
509
-		challengeStr = challengeStr[end:]
510
-	}
511
-	if lastLen == saslMaxArgLength {
512
-		rb.Add(nil, server.name, "AUTHENTICATE", "+")
481
+	for _, chunk := range ircutils.EncodeSASLResponse(challenge) {
482
+		rb.Add(nil, server.name, "AUTHENTICATE", chunk)
513
 	}
483
 	}
514
 }
484
 }
515
 
485
 

+ 105
- 0
vendor/github.com/ergochat/irc-go/ircutils/sasl.go Parādīt failu

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

+ 1
- 3
vendor/modules.txt Parādīt failu

16
 # github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881
16
 # github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881
17
 ## explicit; go 1.18
17
 ## explicit; go 1.18
18
 github.com/ergochat/go-ident
18
 github.com/ergochat/go-ident
19
-# github.com/ergochat/irc-go v0.4.0
19
+# github.com/ergochat/irc-go v0.5.0-rc1
20
 ## explicit; go 1.15
20
 ## explicit; go 1.15
21
 github.com/ergochat/irc-go/ircfmt
21
 github.com/ergochat/irc-go/ircfmt
22
 github.com/ergochat/irc-go/ircmsg
22
 github.com/ergochat/irc-go/ircmsg
30
 # github.com/gofrs/flock v0.8.1
30
 # github.com/gofrs/flock v0.8.1
31
 ## explicit
31
 ## explicit
32
 github.com/gofrs/flock
32
 github.com/gofrs/flock
33
-# github.com/golang-jwt/jwt v3.2.2+incompatible
34
-## explicit
35
 # github.com/golang-jwt/jwt/v5 v5.2.0
33
 # github.com/golang-jwt/jwt/v5 v5.2.0
36
 ## explicit; go 1.18
34
 ## explicit; go 1.18
37
 github.com/golang-jwt/jwt/v5
35
 github.com/golang-jwt/jwt/v5

Notiek ielāde…
Atcelt
Saglabāt