浏览代码

update resume support to draft/resume-0.3

tags/v1.0.0-rc1
Shivaram Lingamneni 5 年前
父节点
当前提交
afe94d43c3
共有 10 个文件被更改,包括 121 次插入47 次删除
  1. 1
    1
      gencapdefs.py
  2. 2
    2
      irc/caps/defs.go
  3. 6
    30
      irc/client.go
  4. 1
    1
      irc/commands.go
  5. 8
    2
      irc/getters.go
  6. 8
    10
      irc/handlers.go
  7. 87
    0
      irc/resume.go
  8. 3
    0
      irc/server.go
  9. 4
    0
      irc/utils/crypto.go
  10. 1
    1
      irc/utils/crypto_test.go

+ 1
- 1
gencapdefs.py 查看文件

@@ -107,7 +107,7 @@ CAPDEFS = [
107 107
     ),
108 108
     CapDef(
109 109
         identifier="Resume",
110
-        name="draft/resume-0.2",
110
+        name="draft/resume-0.3",
111 111
         url="https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md",
112 112
         standard="proposed IRCv3",
113 113
     ),

+ 2
- 2
irc/caps/defs.go 查看文件

@@ -73,7 +73,7 @@ const (
73 73
 	// https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md
74 74
 	Rename Capability = iota
75 75
 
76
-	// Resume is the proposed IRCv3 capability named "draft/resume-0.2":
76
+	// Resume is the proposed IRCv3 capability named "draft/resume-0.3":
77 77
 	// https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md
78 78
 	Resume Capability = iota
79 79
 
@@ -112,7 +112,7 @@ var (
112 112
 		"draft/message-tags-0.2",
113 113
 		"multi-prefix",
114 114
 		"draft/rename",
115
-		"draft/resume-0.2",
115
+		"draft/resume-0.3",
116 116
 		"sasl",
117 117
 		"server-time",
118 118
 		"sts",

+ 6
- 30
irc/client.go 查看文件

@@ -37,8 +37,6 @@ const (
37 37
 // when completing the registration, and when rejoining channels.
38 38
 type ResumeDetails struct {
39 39
 	OldClient         *Client
40
-	OldNick           string
41
-	OldNickMask       string
42 40
 	PresentedToken    string
43 41
 	Timestamp         time.Time
44 42
 	ResumedAt         time.Time
@@ -86,7 +84,7 @@ type Client struct {
86 84
 	realIP             net.IP
87 85
 	registered         bool
88 86
 	resumeDetails      *ResumeDetails
89
-	resumeToken        string
87
+	resumeID           string
90 88
 	saslInProgress     bool
91 89
 	saslMechanism      string
92 90
 	saslValue          string
@@ -385,16 +383,15 @@ func (client *Client) tryResume() (success bool) {
385 383
 		}
386 384
 	}()
387 385
 
388
-	oldnick := client.resumeDetails.OldNick
389 386
 	timestamp := client.resumeDetails.Timestamp
390 387
 	var timestampString string
391 388
 	if !timestamp.IsZero() {
392 389
 		timestampString = timestamp.UTC().Format(IRCv3TimestampFormat)
393 390
 	}
394 391
 
395
-	oldClient := server.clients.Get(oldnick)
392
+	oldClient := server.resumeManager.VerifyToken(client.resumeDetails.PresentedToken)
396 393
 	if oldClient == nil {
397
-		client.Send(nil, server.name, "RESUME", "ERR", oldnick, client.t("Cannot resume connection, old client not found"))
394
+		client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection, token is not valid"))
398 395
 		return
399 396
 	}
400 397
 	oldNick := oldClient.Nick()
@@ -402,13 +399,7 @@ func (client *Client) tryResume() (success bool) {
402 399
 
403 400
 	resumeAllowed := config.Server.AllowPlaintextResume || (oldClient.HasMode(modes.TLS) && client.HasMode(modes.TLS))
404 401
 	if !resumeAllowed {
405
-		client.Send(nil, server.name, "RESUME", "ERR", oldnick, client.t("Cannot resume connection, old and new clients must have TLS"))
406
-		return
407
-	}
408
-
409
-	oldResumeToken := oldClient.ResumeToken()
410
-	if oldResumeToken == "" || !utils.SecretTokensMatch(oldResumeToken, client.resumeDetails.PresentedToken) {
411
-		client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection, invalid resume token"))
402
+		client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection, old and new clients must have TLS"))
412 403
 		return
413 404
 	}
414 405
 
@@ -896,6 +887,8 @@ func (client *Client) destroy(beingResumed bool) {
896 887
 		client.server.connectionLimiter.RemoveClient(ipaddr)
897 888
 	}
898 889
 
890
+	client.server.resumeManager.Delete(client)
891
+
899 892
 	// alert monitors
900 893
 	client.server.monitorManager.AlertAbout(client, false)
901 894
 	// clean up monitor state
@@ -1120,23 +1113,6 @@ func (client *Client) removeChannel(channel *Channel) {
1120 1113
 	client.stateMutex.Unlock()
1121 1114
 }
1122 1115
 
1123
-// Ensures the client has a cryptographically secure resume token, and returns
1124
-// its value. An error is returned if a token was previously assigned.
1125
-func (client *Client) generateResumeToken() (token string, err error) {
1126
-	newToken := utils.GenerateSecretToken()
1127
-
1128
-	client.stateMutex.Lock()
1129
-	defer client.stateMutex.Unlock()
1130
-
1131
-	if client.resumeToken == "" {
1132
-		client.resumeToken = newToken
1133
-	} else {
1134
-		err = errResumeTokenAlreadySet
1135
-	}
1136
-
1137
-	return client.resumeToken, err
1138
-}
1139
-
1140 1116
 // Records that the client has been invited to join an invite-only channel
1141 1117
 func (client *Client) Invite(casefoldedChannel string) {
1142 1118
 	client.stateMutex.Lock()

+ 1
- 1
irc/commands.go 查看文件

@@ -231,7 +231,7 @@ func init() {
231 231
 		"RESUME": {
232 232
 			handler:      resumeHandler,
233 233
 			usablePreReg: true,
234
-			minParams:    2,
234
+			minParams:    1,
235 235
 		},
236 236
 		"SAJOIN": {
237 237
 			handler:   sajoinHandler,

+ 8
- 2
irc/getters.go 查看文件

@@ -109,10 +109,16 @@ func (client *Client) uniqueIdentifiers() (nickCasefolded string, skeleton strin
109 109
 	return client.nickCasefolded, client.skeleton
110 110
 }
111 111
 
112
-func (client *Client) ResumeToken() string {
112
+func (client *Client) ResumeID() string {
113 113
 	client.stateMutex.RLock()
114 114
 	defer client.stateMutex.RUnlock()
115
-	return client.resumeToken
115
+	return client.resumeID
116
+}
117
+
118
+func (client *Client) SetResumeID(id string) {
119
+	client.stateMutex.Lock()
120
+	defer client.stateMutex.Unlock()
121
+	client.resumeID = id
116 122
 }
117 123
 
118 124
 func (client *Client) Oper() *Oper {

+ 8
- 10
irc/handlers.go 查看文件

@@ -508,8 +508,8 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
508 508
 		// if this is the first time the client is requesting a resume token,
509 509
 		// send it to them
510 510
 		if toAdd.Has(caps.Resume) {
511
-			token, err := client.generateResumeToken()
512
-			if err == nil {
511
+			token := server.resumeManager.GenerateToken(client)
512
+			if token != "" {
513 513
 				rb.Add(nil, server.name, "RESUME", "TOKEN", token)
514 514
 			}
515 515
 		}
@@ -2258,28 +2258,26 @@ func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
2258 2258
 	return false
2259 2259
 }
2260 2260
 
2261
-// RESUME <oldnick> <token> [timestamp]
2261
+// RESUME <token> [timestamp]
2262 2262
 func resumeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
2263
-	oldnick := msg.Params[0]
2264
-	token := msg.Params[1]
2263
+	token := msg.Params[0]
2265 2264
 
2266 2265
 	if client.registered {
2267
-		rb.Add(nil, server.name, ERR_CANNOT_RESUME, oldnick, client.t("Cannot resume connection, connection registration has already been completed"))
2266
+		rb.Add(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection, connection registration has already been completed"))
2268 2267
 		return false
2269 2268
 	}
2270 2269
 
2271 2270
 	var timestamp time.Time
2272
-	if 2 < len(msg.Params) {
2273
-		ts, err := time.Parse(IRCv3TimestampFormat, msg.Params[2])
2271
+	if 1 < len(msg.Params) {
2272
+		ts, err := time.Parse(IRCv3TimestampFormat, msg.Params[1])
2274 2273
 		if err == nil {
2275 2274
 			timestamp = ts
2276 2275
 		} else {
2277
-			rb.Add(nil, server.name, ERR_CANNOT_RESUME, oldnick, client.t("Timestamp is not in 2006-01-02T15:04:05.999Z format, ignoring it"))
2276
+			rb.Add(nil, server.name, "RESUME", "ERR", client.t("Timestamp is not in 2006-01-02T15:04:05.999Z format, ignoring it"))
2278 2277
 		}
2279 2278
 	}
2280 2279
 
2281 2280
 	client.resumeDetails = &ResumeDetails{
2282
-		OldNick:        oldnick,
2283 2281
 		Timestamp:      timestamp,
2284 2282
 		PresentedToken: token,
2285 2283
 	}

+ 87
- 0
irc/resume.go 查看文件

@@ -0,0 +1,87 @@
1
+// Copyright (c) 2019 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2
+// released under the MIT license
3
+
4
+package irc
5
+
6
+import (
7
+	"sync"
8
+
9
+	"github.com/oragono/oragono/irc/utils"
10
+)
11
+
12
+// implements draft/resume-0.3, in particular the issuing, management, and verification
13
+// of resume tokens with two components: a unique ID and a secret key
14
+
15
+type resumeTokenPair struct {
16
+	client *Client
17
+	secret string
18
+}
19
+
20
+type ResumeManager struct {
21
+	sync.RWMutex // level 2
22
+
23
+	resumeIDtoCreds map[string]resumeTokenPair
24
+	server          *Server
25
+}
26
+
27
+func (rm *ResumeManager) Initialize(server *Server) {
28
+	rm.resumeIDtoCreds = make(map[string]resumeTokenPair)
29
+	rm.server = server
30
+}
31
+
32
+// GenerateToken generates a resume token for a client. If the client has
33
+// already been assigned one, it returns "".
34
+func (rm *ResumeManager) GenerateToken(client *Client) (token string) {
35
+	id := utils.GenerateSecretToken()
36
+	secret := utils.GenerateSecretToken()
37
+
38
+	rm.Lock()
39
+	defer rm.Unlock()
40
+
41
+	if client.ResumeID() != "" {
42
+		return
43
+	}
44
+
45
+	client.SetResumeID(id)
46
+	rm.resumeIDtoCreds[id] = resumeTokenPair{
47
+		client: client,
48
+		secret: secret,
49
+	}
50
+
51
+	return id + secret
52
+}
53
+
54
+// VerifyToken looks up the client corresponding to a resume token, returning
55
+// nil if there is no such client or the token is invalid.
56
+func (rm *ResumeManager) VerifyToken(token string) (client *Client) {
57
+	if len(token) != 2*utils.SecretTokenLength {
58
+		return
59
+	}
60
+
61
+	rm.RLock()
62
+	defer rm.RUnlock()
63
+
64
+	id := token[:utils.SecretTokenLength]
65
+	pair, ok := rm.resumeIDtoCreds[id]
66
+	if ok {
67
+		if utils.SecretTokensMatch(pair.secret, token[utils.SecretTokenLength:]) {
68
+			// disallow resume of an unregistered client; this prevents the use of
69
+			// resume as an auth bypass
70
+			if pair.client.Registered() {
71
+				return pair.client
72
+			}
73
+		}
74
+	}
75
+	return
76
+}
77
+
78
+// Delete stops tracking a client's resume token.
79
+func (rm *ResumeManager) Delete(client *Client) {
80
+	rm.Lock()
81
+	defer rm.Unlock()
82
+
83
+	currentID := client.ResumeID()
84
+	if currentID != "" {
85
+		delete(rm.resumeIDtoCreds, currentID)
86
+	}
87
+}

+ 3
- 0
irc/server.go 查看文件

@@ -88,6 +88,7 @@ type Server struct {
88 88
 	rehashMutex            sync.Mutex // tier 4
89 89
 	rehashSignal           chan os.Signal
90 90
 	pprofServer            *http.Server
91
+	resumeManager          ResumeManager
91 92
 	signals                chan os.Signal
92 93
 	snomasks               *SnoManager
93 94
 	store                  *buntdb.DB
@@ -130,6 +131,8 @@ func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
130 131
 		semaphores:          NewServerSemaphores(),
131 132
 	}
132 133
 
134
+	server.resumeManager.Initialize(server)
135
+
133 136
 	if err := server.applyConfig(config, true); err != nil {
134 137
 		return nil, err
135 138
 	}

+ 4
- 0
irc/utils/crypto.go 查看文件

@@ -14,6 +14,10 @@ var (
14 14
 	b32encoder = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567").WithPadding(base32.NoPadding)
15 15
 )
16 16
 
17
+const (
18
+	SecretTokenLength = 26
19
+)
20
+
17 21
 // generate a secret token that cannot be brute-forced via online attacks
18 22
 func GenerateSecretToken() string {
19 23
 	// 128 bits of entropy are enough to resist any online attack:

+ 1
- 1
irc/utils/crypto_test.go 查看文件

@@ -16,7 +16,7 @@ const (
16 16
 
17 17
 func TestGenerateSecretToken(t *testing.T) {
18 18
 	token := GenerateSecretToken()
19
-	if len(token) < 22 {
19
+	if len(token) != SecretTokenLength {
20 20
 		t.Errorf("bad token: %v", token)
21 21
 	}
22 22
 }

正在加载...
取消
保存