Browse Source

fix #307

tags/v2.4.0-rc1
Shivaram Lingamneni 3 years ago
parent
commit
bd40b46639
5 changed files with 223 additions and 138 deletions
  1. 19
    5
      irc/channel.go
  2. 0
    133
      irc/client_lookup_set.go
  3. 1
    0
      irc/config.go
  4. 167
    0
      irc/usermaskset.go
  5. 36
    0
      irc/usermaskset_test.go

+ 19
- 5
irc/channel.go View File

@@ -1227,24 +1227,38 @@ func (channel *Channel) SetTopic(client *Client, topic string, rb *ResponseBuffe
1227 1227
 // CanSpeak returns true if the client can speak on this channel, otherwise it returns false along with the channel mode preventing the client from speaking.
1228 1228
 func (channel *Channel) CanSpeak(client *Client) (bool, modes.Mode) {
1229 1229
 	channel.stateMutex.RLock()
1230
-	defer channel.stateMutex.RUnlock()
1230
+	clientModes, hasClient := channel.members[client]
1231
+	channel.stateMutex.RUnlock()
1231 1232
 
1232
-	_, hasClient := channel.members[client]
1233
-	if channel.flags.HasMode(modes.NoOutside) && !hasClient {
1233
+	if !hasClient && channel.flags.HasMode(modes.NoOutside) {
1234
+		// TODO: enforce regular +b bans on -n channels?
1234 1235
 		return false, modes.NoOutside
1235 1236
 	}
1236
-	if channel.flags.HasMode(modes.Moderated) && !channel.ClientIsAtLeast(client, modes.Voice) {
1237
+	if channel.isMuted(client) && clientModes.HighestChannelUserMode() == modes.Mode(0) {
1238
+		return false, modes.BanMask
1239
+	}
1240
+	if channel.flags.HasMode(modes.Moderated) && clientModes.HighestChannelUserMode() == modes.Mode(0) {
1237 1241
 		return false, modes.Moderated
1238 1242
 	}
1239 1243
 	if channel.flags.HasMode(modes.RegisteredOnly) && client.Account() == "" {
1240 1244
 		return false, modes.RegisteredOnly
1241 1245
 	}
1242
-	if channel.flags.HasMode(modes.RegisteredOnlySpeak) && client.Account() == "" && !channel.ClientIsAtLeast(client, modes.Voice) {
1246
+	if channel.flags.HasMode(modes.RegisteredOnlySpeak) && client.Account() == "" &&
1247
+		clientModes.HighestChannelUserMode() != modes.Mode(0) {
1243 1248
 		return false, modes.RegisteredOnlySpeak
1244 1249
 	}
1245 1250
 	return true, modes.Mode('?')
1246 1251
 }
1247 1252
 
1253
+func (channel *Channel) isMuted(client *Client) bool {
1254
+	muteRe := channel.lists[modes.BanMask].MuteRegexp()
1255
+	if muteRe == nil {
1256
+		return false
1257
+	}
1258
+	nuh := client.NickMaskString()
1259
+	return muteRe.MatchString(nuh) && !channel.lists[modes.ExceptMask].MatchMute(nuh)
1260
+}
1261
+
1248 1262
 func msgCommandToHistType(command string) (history.ItemType, error) {
1249 1263
 	switch command {
1250 1264
 	case "PRIVMSG":

+ 0
- 133
irc/client_lookup_set.go View File

@@ -5,10 +5,8 @@
5 5
 package irc
6 6
 
7 7
 import (
8
-	"regexp"
9 8
 	"strings"
10 9
 	"sync"
11
-	"time"
12 10
 
13 11
 	"github.com/oragono/oragono/irc/caps"
14 12
 	"github.com/oragono/oragono/irc/modes"
@@ -306,134 +304,3 @@ func (clients *ClientManager) FindAll(userhost string) (set ClientSet) {
306 304
 
307 305
 	return set
308 306
 }
309
-
310
-//
311
-// usermask to regexp
312
-//
313
-
314
-//TODO(dan): move this over to generally using glob syntax instead?
315
-// kinda more expected in normal ban/etc masks, though regex is useful (probably as an extban?)
316
-
317
-type MaskInfo struct {
318
-	TimeCreated     time.Time
319
-	CreatorNickmask string
320
-	CreatorAccount  string
321
-}
322
-
323
-// UserMaskSet holds a set of client masks and lets you match  hostnames to them.
324
-type UserMaskSet struct {
325
-	sync.RWMutex
326
-	serialCacheUpdateMutex sync.Mutex
327
-	masks                  map[string]MaskInfo
328
-	regexp                 *regexp.Regexp
329
-}
330
-
331
-func NewUserMaskSet() *UserMaskSet {
332
-	return new(UserMaskSet)
333
-}
334
-
335
-// Add adds the given mask to this set.
336
-func (set *UserMaskSet) Add(mask, creatorNickmask, creatorAccount string) (maskAdded string, err error) {
337
-	casefoldedMask, err := CanonicalizeMaskWildcard(mask)
338
-	if err != nil {
339
-		return
340
-	}
341
-
342
-	set.serialCacheUpdateMutex.Lock()
343
-	defer set.serialCacheUpdateMutex.Unlock()
344
-
345
-	set.Lock()
346
-	if set.masks == nil {
347
-		set.masks = make(map[string]MaskInfo)
348
-	}
349
-	_, present := set.masks[casefoldedMask]
350
-	if !present {
351
-		maskAdded = casefoldedMask
352
-		set.masks[casefoldedMask] = MaskInfo{
353
-			TimeCreated:     time.Now().UTC(),
354
-			CreatorNickmask: creatorNickmask,
355
-			CreatorAccount:  creatorAccount,
356
-		}
357
-	}
358
-	set.Unlock()
359
-
360
-	if !present {
361
-		set.setRegexp()
362
-	}
363
-	return
364
-}
365
-
366
-// Remove removes the given mask from this set.
367
-func (set *UserMaskSet) Remove(mask string) (maskRemoved string, err error) {
368
-	mask, err = CanonicalizeMaskWildcard(mask)
369
-	if err != nil {
370
-		return
371
-	}
372
-
373
-	set.serialCacheUpdateMutex.Lock()
374
-	defer set.serialCacheUpdateMutex.Unlock()
375
-
376
-	set.Lock()
377
-	_, removed := set.masks[mask]
378
-	if removed {
379
-		maskRemoved = mask
380
-		delete(set.masks, mask)
381
-	}
382
-	set.Unlock()
383
-
384
-	if removed {
385
-		set.setRegexp()
386
-	}
387
-	return
388
-}
389
-
390
-func (set *UserMaskSet) SetMasks(masks map[string]MaskInfo) {
391
-	set.Lock()
392
-	set.masks = masks
393
-	set.Unlock()
394
-	set.setRegexp()
395
-}
396
-
397
-func (set *UserMaskSet) Masks() (result map[string]MaskInfo) {
398
-	set.RLock()
399
-	defer set.RUnlock()
400
-
401
-	result = make(map[string]MaskInfo, len(set.masks))
402
-	for mask, info := range set.masks {
403
-		result[mask] = info
404
-	}
405
-	return
406
-}
407
-
408
-// Match matches the given n!u@h.
409
-func (set *UserMaskSet) Match(userhost string) bool {
410
-	set.RLock()
411
-	regexp := set.regexp
412
-	set.RUnlock()
413
-
414
-	if regexp == nil {
415
-		return false
416
-	}
417
-	return regexp.MatchString(userhost)
418
-}
419
-
420
-func (set *UserMaskSet) Length() int {
421
-	set.RLock()
422
-	defer set.RUnlock()
423
-	return len(set.masks)
424
-}
425
-
426
-func (set *UserMaskSet) setRegexp() {
427
-	set.RLock()
428
-	maskExprs := make([]string, len(set.masks))
429
-	for mask := range set.masks {
430
-		maskExprs = append(maskExprs, mask)
431
-	}
432
-	set.RUnlock()
433
-
434
-	re, _ := utils.CompileMasks(maskExprs)
435
-
436
-	set.Lock()
437
-	set.regexp = re
438
-	set.Unlock()
439
-}

+ 1
- 0
irc/config.go View File

@@ -1298,6 +1298,7 @@ func (config *Config) generateISupport() (err error) {
1298 1298
 	if config.Extjwt.Default.Enabled() || len(config.Extjwt.Services) != 0 {
1299 1299
 		isupport.Add("EXTJWT", "1")
1300 1300
 	}
1301
+	isupport.Add("EXTBAN", ",m")
1301 1302
 	isupport.Add("INVEX", "")
1302 1303
 	isupport.Add("KICKLEN", strconv.Itoa(config.Limits.KickLen))
1303 1304
 	isupport.Add("MAXLIST", fmt.Sprintf("beI:%s", strconv.Itoa(config.Limits.ChanListModes)))

+ 167
- 0
irc/usermaskset.go View File

@@ -0,0 +1,167 @@
1
+// Copyright (c) 2012-2014 Jeremy Latt
2
+// Copyright (c) 2016-2018 Daniel Oaks
3
+// Copyright (c) 2019-2020 Shivaram Lingamneni
4
+// released under the MIT license
5
+
6
+package irc
7
+
8
+import (
9
+	"regexp"
10
+	"strings"
11
+	"sync"
12
+	"sync/atomic"
13
+	"time"
14
+	"unsafe"
15
+
16
+	"github.com/oragono/oragono/irc/utils"
17
+)
18
+
19
+type MaskInfo struct {
20
+	TimeCreated     time.Time
21
+	CreatorNickmask string
22
+	CreatorAccount  string
23
+}
24
+
25
+// UserMaskSet holds a set of client masks and lets you match  hostnames to them.
26
+type UserMaskSet struct {
27
+	sync.RWMutex
28
+	serialCacheUpdateMutex sync.Mutex
29
+	masks                  map[string]MaskInfo
30
+	regexp                 unsafe.Pointer
31
+	muteRegexp             unsafe.Pointer
32
+}
33
+
34
+func NewUserMaskSet() *UserMaskSet {
35
+	return new(UserMaskSet)
36
+}
37
+
38
+// Add adds the given mask to this set.
39
+func (set *UserMaskSet) Add(mask, creatorNickmask, creatorAccount string) (maskAdded string, err error) {
40
+	casefoldedMask, err := CanonicalizeMaskWildcard(mask)
41
+	if err != nil {
42
+		return
43
+	}
44
+
45
+	set.serialCacheUpdateMutex.Lock()
46
+	defer set.serialCacheUpdateMutex.Unlock()
47
+
48
+	set.Lock()
49
+	if set.masks == nil {
50
+		set.masks = make(map[string]MaskInfo)
51
+	}
52
+	_, present := set.masks[casefoldedMask]
53
+	if !present {
54
+		maskAdded = casefoldedMask
55
+		set.masks[casefoldedMask] = MaskInfo{
56
+			TimeCreated:     time.Now().UTC(),
57
+			CreatorNickmask: creatorNickmask,
58
+			CreatorAccount:  creatorAccount,
59
+		}
60
+	}
61
+	set.Unlock()
62
+
63
+	if !present {
64
+		set.setRegexp()
65
+	}
66
+	return
67
+}
68
+
69
+// Remove removes the given mask from this set.
70
+func (set *UserMaskSet) Remove(mask string) (maskRemoved string, err error) {
71
+	mask, err = CanonicalizeMaskWildcard(mask)
72
+	if err != nil {
73
+		return
74
+	}
75
+
76
+	set.serialCacheUpdateMutex.Lock()
77
+	defer set.serialCacheUpdateMutex.Unlock()
78
+
79
+	set.Lock()
80
+	_, removed := set.masks[mask]
81
+	if removed {
82
+		maskRemoved = mask
83
+		delete(set.masks, mask)
84
+	}
85
+	set.Unlock()
86
+
87
+	if removed {
88
+		set.setRegexp()
89
+	}
90
+	return
91
+}
92
+
93
+func (set *UserMaskSet) SetMasks(masks map[string]MaskInfo) {
94
+	set.Lock()
95
+	set.masks = masks
96
+	set.Unlock()
97
+	set.setRegexp()
98
+}
99
+
100
+func (set *UserMaskSet) Masks() (result map[string]MaskInfo) {
101
+	set.RLock()
102
+	defer set.RUnlock()
103
+
104
+	result = make(map[string]MaskInfo, len(set.masks))
105
+	for mask, info := range set.masks {
106
+		result[mask] = info
107
+	}
108
+	return
109
+}
110
+
111
+// Match matches the given n!u@h against the standard (non-ext) bans.
112
+func (set *UserMaskSet) Match(userhost string) bool {
113
+	regexp := (*regexp.Regexp)(atomic.LoadPointer(&set.regexp))
114
+
115
+	if regexp == nil {
116
+		return false
117
+	}
118
+	return regexp.MatchString(userhost)
119
+}
120
+
121
+// MatchMute matches the given NUH against the mute extbans.
122
+func (set *UserMaskSet) MatchMute(userhost string) bool {
123
+	regexp := set.MuteRegexp()
124
+
125
+	if regexp == nil {
126
+		return false
127
+	}
128
+	return regexp.MatchString(userhost)
129
+}
130
+
131
+func (set *UserMaskSet) MuteRegexp() *regexp.Regexp {
132
+	return (*regexp.Regexp)(atomic.LoadPointer(&set.muteRegexp))
133
+}
134
+
135
+func (set *UserMaskSet) Length() int {
136
+	set.RLock()
137
+	defer set.RUnlock()
138
+	return len(set.masks)
139
+}
140
+
141
+func (set *UserMaskSet) setRegexp() {
142
+	set.RLock()
143
+	maskExprs := make([]string, 0, len(set.masks))
144
+	var muteExprs []string
145
+	for mask := range set.masks {
146
+		if strings.HasPrefix(mask, "m:") {
147
+			muteExprs = append(muteExprs, mask[2:])
148
+		} else {
149
+			maskExprs = append(maskExprs, mask)
150
+		}
151
+	}
152
+	set.RUnlock()
153
+
154
+	compileMasks := func(masks []string) *regexp.Regexp {
155
+		if len(masks) == 0 {
156
+			return nil
157
+		}
158
+		re, _ := utils.CompileMasks(masks)
159
+		return re
160
+	}
161
+
162
+	re := compileMasks(maskExprs)
163
+	muteRe := compileMasks(muteExprs)
164
+
165
+	atomic.StorePointer(&set.regexp, unsafe.Pointer(re))
166
+	atomic.StorePointer(&set.muteRegexp, unsafe.Pointer(muteRe))
167
+}

+ 36
- 0
irc/usermaskset_test.go View File

@@ -0,0 +1,36 @@
1
+// Copyright (c) 2020 Shivaram Lingamneni
2
+// released under the MIT license
3
+
4
+package irc
5
+
6
+import (
7
+	"testing"
8
+)
9
+
10
+func TestUserMaskSet(t *testing.T) {
11
+	s := NewUserMaskSet()
12
+
13
+	if s.Match("horse!~evan@tor-network.onion") {
14
+		t.Errorf("empty set should not match anything")
15
+	}
16
+
17
+	s.Add("m:horse!*@*", "", "")
18
+	if s.Match("horse!~evan@tor-network.onion") {
19
+		t.Errorf("mute extbans should not Match(), only MatchMute()")
20
+	}
21
+
22
+	s.Add("*!~evan@*", "", "")
23
+	if !s.Match("horse!~evan@tor-network.onion") {
24
+		t.Errorf("expected Match() failed")
25
+	}
26
+	if s.Match("horse!~horse@tor-network.onion") {
27
+		t.Errorf("unexpected Match() succeeded")
28
+	}
29
+
30
+	if !s.MatchMute("horse!~evan@tor-network.onion") {
31
+		t.Errorf("expected MatchMute() failed")
32
+	}
33
+	if s.MatchMute("evan!~evan@tor-network.onion") {
34
+		t.Errorf("unexpected MatchMute() succeeded")
35
+	}
36
+}

Loading…
Cancel
Save