浏览代码

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

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

73
 	// https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md
73
 	// https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md
74
 	Rename Capability = iota
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
 	// https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md
77
 	// https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md
78
 	Resume Capability = iota
78
 	Resume Capability = iota
79
 
79
 
112
 		"draft/message-tags-0.2",
112
 		"draft/message-tags-0.2",
113
 		"multi-prefix",
113
 		"multi-prefix",
114
 		"draft/rename",
114
 		"draft/rename",
115
-		"draft/resume-0.2",
115
+		"draft/resume-0.3",
116
 		"sasl",
116
 		"sasl",
117
 		"server-time",
117
 		"server-time",
118
 		"sts",
118
 		"sts",

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

37
 // when completing the registration, and when rejoining channels.
37
 // when completing the registration, and when rejoining channels.
38
 type ResumeDetails struct {
38
 type ResumeDetails struct {
39
 	OldClient         *Client
39
 	OldClient         *Client
40
-	OldNick           string
41
-	OldNickMask       string
42
 	PresentedToken    string
40
 	PresentedToken    string
43
 	Timestamp         time.Time
41
 	Timestamp         time.Time
44
 	ResumedAt         time.Time
42
 	ResumedAt         time.Time
86
 	realIP             net.IP
84
 	realIP             net.IP
87
 	registered         bool
85
 	registered         bool
88
 	resumeDetails      *ResumeDetails
86
 	resumeDetails      *ResumeDetails
89
-	resumeToken        string
87
+	resumeID           string
90
 	saslInProgress     bool
88
 	saslInProgress     bool
91
 	saslMechanism      string
89
 	saslMechanism      string
92
 	saslValue          string
90
 	saslValue          string
385
 		}
383
 		}
386
 	}()
384
 	}()
387
 
385
 
388
-	oldnick := client.resumeDetails.OldNick
389
 	timestamp := client.resumeDetails.Timestamp
386
 	timestamp := client.resumeDetails.Timestamp
390
 	var timestampString string
387
 	var timestampString string
391
 	if !timestamp.IsZero() {
388
 	if !timestamp.IsZero() {
392
 		timestampString = timestamp.UTC().Format(IRCv3TimestampFormat)
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
 	if oldClient == nil {
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
 		return
395
 		return
399
 	}
396
 	}
400
 	oldNick := oldClient.Nick()
397
 	oldNick := oldClient.Nick()
402
 
399
 
403
 	resumeAllowed := config.Server.AllowPlaintextResume || (oldClient.HasMode(modes.TLS) && client.HasMode(modes.TLS))
400
 	resumeAllowed := config.Server.AllowPlaintextResume || (oldClient.HasMode(modes.TLS) && client.HasMode(modes.TLS))
404
 	if !resumeAllowed {
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
 		return
403
 		return
413
 	}
404
 	}
414
 
405
 
896
 		client.server.connectionLimiter.RemoveClient(ipaddr)
887
 		client.server.connectionLimiter.RemoveClient(ipaddr)
897
 	}
888
 	}
898
 
889
 
890
+	client.server.resumeManager.Delete(client)
891
+
899
 	// alert monitors
892
 	// alert monitors
900
 	client.server.monitorManager.AlertAbout(client, false)
893
 	client.server.monitorManager.AlertAbout(client, false)
901
 	// clean up monitor state
894
 	// clean up monitor state
1120
 	client.stateMutex.Unlock()
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
 // Records that the client has been invited to join an invite-only channel
1116
 // Records that the client has been invited to join an invite-only channel
1141
 func (client *Client) Invite(casefoldedChannel string) {
1117
 func (client *Client) Invite(casefoldedChannel string) {
1142
 	client.stateMutex.Lock()
1118
 	client.stateMutex.Lock()

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

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

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

109
 	return client.nickCasefolded, client.skeleton
109
 	return client.nickCasefolded, client.skeleton
110
 }
110
 }
111
 
111
 
112
-func (client *Client) ResumeToken() string {
112
+func (client *Client) ResumeID() string {
113
 	client.stateMutex.RLock()
113
 	client.stateMutex.RLock()
114
 	defer client.stateMutex.RUnlock()
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
 func (client *Client) Oper() *Oper {
124
 func (client *Client) Oper() *Oper {

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

508
 		// if this is the first time the client is requesting a resume token,
508
 		// if this is the first time the client is requesting a resume token,
509
 		// send it to them
509
 		// send it to them
510
 		if toAdd.Has(caps.Resume) {
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
 				rb.Add(nil, server.name, "RESUME", "TOKEN", token)
513
 				rb.Add(nil, server.name, "RESUME", "TOKEN", token)
514
 			}
514
 			}
515
 		}
515
 		}
2258
 	return false
2258
 	return false
2259
 }
2259
 }
2260
 
2260
 
2261
-// RESUME <oldnick> <token> [timestamp]
2261
+// RESUME <token> [timestamp]
2262
 func resumeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
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
 	if client.registered {
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
 		return false
2267
 		return false
2269
 	}
2268
 	}
2270
 
2269
 
2271
 	var timestamp time.Time
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
 		if err == nil {
2273
 		if err == nil {
2275
 			timestamp = ts
2274
 			timestamp = ts
2276
 		} else {
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
 	client.resumeDetails = &ResumeDetails{
2280
 	client.resumeDetails = &ResumeDetails{
2282
-		OldNick:        oldnick,
2283
 		Timestamp:      timestamp,
2281
 		Timestamp:      timestamp,
2284
 		PresentedToken: token,
2282
 		PresentedToken: token,
2285
 	}
2283
 	}

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

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
 	rehashMutex            sync.Mutex // tier 4
88
 	rehashMutex            sync.Mutex // tier 4
89
 	rehashSignal           chan os.Signal
89
 	rehashSignal           chan os.Signal
90
 	pprofServer            *http.Server
90
 	pprofServer            *http.Server
91
+	resumeManager          ResumeManager
91
 	signals                chan os.Signal
92
 	signals                chan os.Signal
92
 	snomasks               *SnoManager
93
 	snomasks               *SnoManager
93
 	store                  *buntdb.DB
94
 	store                  *buntdb.DB
130
 		semaphores:          NewServerSemaphores(),
131
 		semaphores:          NewServerSemaphores(),
131
 	}
132
 	}
132
 
133
 
134
+	server.resumeManager.Initialize(server)
135
+
133
 	if err := server.applyConfig(config, true); err != nil {
136
 	if err := server.applyConfig(config, true); err != nil {
134
 		return nil, err
137
 		return nil, err
135
 	}
138
 	}

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

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

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

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

正在加载...
取消
保存