Browse Source

fix #644

tags/v1.2.0-rc1
Shivaram Lingamneni 4 years ago
parent
commit
332f0d8d91
9 changed files with 166 additions and 98 deletions
  1. 10
    24
      irc/channel.go
  2. 15
    15
      irc/channelreg.go
  3. 45
    43
      irc/client_lookup_set.go
  4. 66
    1
      irc/database.go
  5. 1
    1
      irc/handlers.go
  6. 8
    10
      irc/modes.go
  7. 1
    1
      irc/numerics.go
  8. 19
    3
      irc/strings.go
  9. 1
    0
      irc/strings_test.go

+ 10
- 24
irc/channel.go View File

@@ -124,18 +124,12 @@ func (channel *Channel) applyRegInfo(chanReg RegisteredChannel) {
124 124
 	for _, mode := range chanReg.Modes {
125 125
 		channel.flags.SetMode(mode, true)
126 126
 	}
127
-	for _, mask := range chanReg.Banlist {
128
-		channel.lists[modes.BanMask].Add(mask)
129
-	}
130
-	for _, mask := range chanReg.Exceptlist {
131
-		channel.lists[modes.ExceptMask].Add(mask)
132
-	}
133
-	for _, mask := range chanReg.Invitelist {
134
-		channel.lists[modes.InviteMask].Add(mask)
135
-	}
136 127
 	for account, mode := range chanReg.AccountToUMode {
137 128
 		channel.accountToUMode[account] = mode
138 129
 	}
130
+	channel.lists[modes.BanMask].SetMasks(chanReg.Bans)
131
+	channel.lists[modes.InviteMask].SetMasks(chanReg.Invites)
132
+	channel.lists[modes.ExceptMask].SetMasks(chanReg.Excepts)
139 133
 }
140 134
 
141 135
 // obtain a consistent snapshot of the channel state that can be persisted to the DB
@@ -160,15 +154,9 @@ func (channel *Channel) ExportRegistration(includeFlags uint) (info RegisteredCh
160 154
 	}
161 155
 
162 156
 	if includeFlags&IncludeLists != 0 {
163
-		for mask := range channel.lists[modes.BanMask].masks {
164
-			info.Banlist = append(info.Banlist, mask)
165
-		}
166
-		for mask := range channel.lists[modes.ExceptMask].masks {
167
-			info.Exceptlist = append(info.Exceptlist, mask)
168
-		}
169
-		for mask := range channel.lists[modes.InviteMask].masks {
170
-			info.Invitelist = append(info.Invitelist, mask)
171
-		}
157
+		info.Bans = channel.lists[modes.BanMask].Masks()
158
+		info.Invites = channel.lists[modes.InviteMask].Masks()
159
+		info.Excepts = channel.lists[modes.ExceptMask].Masks()
172 160
 		info.AccountToUMode = make(map[string]modes.Mode)
173 161
 		for account, mode := range channel.accountToUMode {
174 162
 			info.AccountToUMode[account] = mode
@@ -1097,14 +1085,12 @@ func (channel *Channel) ShowMaskList(client *Client, mode modes.Mode, rb *Respon
1097 1085
 	}
1098 1086
 
1099 1087
 	nick := client.Nick()
1100
-	channel.stateMutex.RLock()
1101
-	// XXX don't acquire any new locks in this section, besides Socket.Write
1102
-	for mask := range channel.lists[mode].masks {
1103
-		rb.Add(nil, client.server.name, rpllist, nick, channel.name, mask)
1088
+	chname := channel.Name()
1089
+	for mask, info := range channel.lists[mode].Masks() {
1090
+		rb.Add(nil, client.server.name, rpllist, nick, chname, mask, info.CreatorNickmask, strconv.FormatInt(info.TimeCreated.Unix(), 10))
1104 1091
 	}
1105
-	channel.stateMutex.RUnlock()
1106 1092
 
1107
-	rb.Add(nil, client.server.name, rplendoflist, nick, channel.name, client.t("End of list"))
1093
+	rb.Add(nil, client.server.name, rplendoflist, nick, chname, client.t("End of list"))
1108 1094
 }
1109 1095
 
1110 1096
 // Quit removes the given client from the channel

+ 15
- 15
irc/channelreg.go View File

@@ -88,12 +88,12 @@ type RegisteredChannel struct {
88 88
 	Key string
89 89
 	// AccountToUMode maps user accounts to their persistent channel modes (e.g., +q, +h)
90 90
 	AccountToUMode map[string]modes.Mode
91
-	// Banlist represents the bans set on the channel.
92
-	Banlist []string
93
-	// Exceptlist represents the exceptions set on the channel.
94
-	Exceptlist []string
95
-	// Invitelist represents the invite exceptions set on the channel.
96
-	Invitelist []string
91
+	// Bans represents the bans set on the channel.
92
+	Bans map[string]MaskInfo
93
+	// Excepts represents the exceptions set on the channel.
94
+	Excepts map[string]MaskInfo
95
+	// Invites represents the invite exceptions set on the channel.
96
+	Invites map[string]MaskInfo
97 97
 }
98 98
 
99 99
 // ChannelRegistry manages registered channels.
@@ -180,11 +180,11 @@ func (reg *ChannelRegistry) LoadChannel(nameCasefolded string) (info RegisteredC
180 180
 			modeSlice[i] = modes.Mode(mode)
181 181
 		}
182 182
 
183
-		var banlist []string
183
+		var banlist map[string]MaskInfo
184 184
 		_ = json.Unmarshal([]byte(banlistString), &banlist)
185
-		var exceptlist []string
185
+		var exceptlist map[string]MaskInfo
186 186
 		_ = json.Unmarshal([]byte(exceptlistString), &exceptlist)
187
-		var invitelist []string
187
+		var invitelist map[string]MaskInfo
188 188
 		_ = json.Unmarshal([]byte(invitelistString), &invitelist)
189 189
 		accountToUMode := make(map[string]modes.Mode)
190 190
 		_ = json.Unmarshal([]byte(accountToUModeString), &accountToUMode)
@@ -198,9 +198,9 @@ func (reg *ChannelRegistry) LoadChannel(nameCasefolded string) (info RegisteredC
198 198
 			TopicSetTime:   time.Unix(topicSetTimeInt, 0),
199 199
 			Key:            password,
200 200
 			Modes:          modeSlice,
201
-			Banlist:        banlist,
202
-			Exceptlist:     exceptlist,
203
-			Invitelist:     invitelist,
201
+			Bans:           banlist,
202
+			Excepts:        exceptlist,
203
+			Invites:        invitelist,
204 204
 			AccountToUMode: accountToUMode,
205 205
 		}
206 206
 		return nil
@@ -296,11 +296,11 @@ func (reg *ChannelRegistry) saveChannel(tx *buntdb.Tx, channelInfo RegisteredCha
296 296
 	}
297 297
 
298 298
 	if includeFlags&IncludeLists != 0 {
299
-		banlistString, _ := json.Marshal(channelInfo.Banlist)
299
+		banlistString, _ := json.Marshal(channelInfo.Bans)
300 300
 		tx.Set(fmt.Sprintf(keyChannelBanlist, channelKey), string(banlistString), nil)
301
-		exceptlistString, _ := json.Marshal(channelInfo.Exceptlist)
301
+		exceptlistString, _ := json.Marshal(channelInfo.Excepts)
302 302
 		tx.Set(fmt.Sprintf(keyChannelExceptlist, channelKey), string(exceptlistString), nil)
303
-		invitelistString, _ := json.Marshal(channelInfo.Invitelist)
303
+		invitelistString, _ := json.Marshal(channelInfo.Invites)
304 304
 		tx.Set(fmt.Sprintf(keyChannelInvitelist, channelKey), string(invitelistString), nil)
305 305
 		accountToUModeString, _ := json.Marshal(channelInfo.AccountToUMode)
306 306
 		tx.Set(fmt.Sprintf(keyChannelAccountToUMode, channelKey), string(accountToUModeString), nil)

+ 45
- 43
irc/client_lookup_set.go View File

@@ -5,10 +5,9 @@
5 5
 package irc
6 6
 
7 7
 import (
8
-	"fmt"
9
-	"log"
10 8
 	"regexp"
11 9
 	"strings"
10
+	"time"
12 11
 
13 12
 	"github.com/goshuirc/irc-go/ircmatch"
14 13
 
@@ -251,52 +250,45 @@ func (clients *ClientManager) FindAll(userhost string) (set ClientSet) {
251 250
 //TODO(dan): move this over to generally using glob syntax instead?
252 251
 // kinda more expected in normal ban/etc masks, though regex is useful (probably as an extban?)
253 252
 
253
+type MaskInfo struct {
254
+	TimeCreated     time.Time
255
+	CreatorNickmask string
256
+	CreatorAccount  string
257
+}
258
+
254 259
 // UserMaskSet holds a set of client masks and lets you match  hostnames to them.
255 260
 type UserMaskSet struct {
256 261
 	sync.RWMutex
257
-	masks  map[string]bool
262
+	masks  map[string]MaskInfo
258 263
 	regexp *regexp.Regexp
259 264
 }
260 265
 
261
-// NewUserMaskSet returns a new UserMaskSet.
262 266
 func NewUserMaskSet() *UserMaskSet {
263
-	return &UserMaskSet{
264
-		masks: make(map[string]bool),
265
-	}
267
+	return new(UserMaskSet)
266 268
 }
267 269
 
268 270
 // Add adds the given mask to this set.
269
-func (set *UserMaskSet) Add(mask string) (added bool) {
270
-	casefoldedMask, err := Casefold(mask)
271
+func (set *UserMaskSet) Add(mask, creatorNickmask, creatorAccount string) (added bool) {
272
+	casefoldedMask, err := CanonicalizeMaskWildcard(mask)
271 273
 	if err != nil {
272
-		log.Println(fmt.Sprintf("ERROR: Could not add mask to usermaskset: [%s]", mask))
273 274
 		return false
274 275
 	}
275 276
 
276 277
 	set.Lock()
277
-	added = !set.masks[casefoldedMask]
278
-	if added {
279
-		set.masks[casefoldedMask] = true
278
+	if set.masks == nil {
279
+		set.masks = make(map[string]MaskInfo)
280 280
 	}
281
-	set.Unlock()
282
-
281
+	_, present := set.masks[casefoldedMask]
282
+	added = !present
283 283
 	if added {
284
-		set.setRegexp()
285
-	}
286
-	return
287
-}
288
-
289
-// AddAll adds the given masks to this set.
290
-func (set *UserMaskSet) AddAll(masks []string) (added bool) {
291
-	set.Lock()
292
-	defer set.Unlock()
293
-
294
-	for _, mask := range masks {
295
-		if !added && !set.masks[mask] {
296
-			added = true
284
+		set.masks[casefoldedMask] = MaskInfo{
285
+			TimeCreated:     time.Now().UTC(),
286
+			CreatorNickmask: creatorNickmask,
287
+			CreatorAccount:  creatorAccount,
297 288
 		}
298
-		set.masks[mask] = true
299 289
 	}
290
+	set.Unlock()
291
+
300 292
 	if added {
301 293
 		set.setRegexp()
302 294
 	}
@@ -305,8 +297,13 @@ func (set *UserMaskSet) AddAll(masks []string) (added bool) {
305 297
 
306 298
 // Remove removes the given mask from this set.
307 299
 func (set *UserMaskSet) Remove(mask string) (removed bool) {
300
+	mask, err := CanonicalizeMaskWildcard(mask)
301
+	if err != nil {
302
+		return false
303
+	}
304
+
308 305
 	set.Lock()
309
-	removed = set.masks[mask]
306
+	_, removed = set.masks[mask]
310 307
 	if removed {
311 308
 		delete(set.masks, mask)
312 309
 	}
@@ -318,6 +315,24 @@ func (set *UserMaskSet) Remove(mask string) (removed bool) {
318 315
 	return
319 316
 }
320 317
 
318
+func (set *UserMaskSet) SetMasks(masks map[string]MaskInfo) {
319
+	set.Lock()
320
+	set.masks = masks
321
+	set.Unlock()
322
+	set.setRegexp()
323
+}
324
+
325
+func (set *UserMaskSet) Masks() (result map[string]MaskInfo) {
326
+	set.RLock()
327
+	defer set.RUnlock()
328
+
329
+	result = make(map[string]MaskInfo, len(set.masks))
330
+	for mask, info := range set.masks {
331
+		result[mask] = info
332
+	}
333
+	return
334
+}
335
+
321 336
 // Match matches the given n!u@h.
322 337
 func (set *UserMaskSet) Match(userhost string) bool {
323 338
 	set.RLock()
@@ -330,19 +345,6 @@ func (set *UserMaskSet) Match(userhost string) bool {
330 345
 	return regexp.MatchString(userhost)
331 346
 }
332 347
 
333
-// String returns the masks in this set.
334
-func (set *UserMaskSet) String() string {
335
-	set.RLock()
336
-	masks := make([]string, len(set.masks))
337
-	index := 0
338
-	for mask := range set.masks {
339
-		masks[index] = mask
340
-		index++
341
-	}
342
-	set.RUnlock()
343
-	return strings.Join(masks, " ")
344
-}
345
-
346 348
 func (set *UserMaskSet) Length() int {
347 349
 	set.RLock()
348 350
 	defer set.RUnlock()

+ 66
- 1
irc/database.go View File

@@ -22,7 +22,7 @@ const (
22 22
 	// 'version' of the database schema
23 23
 	keySchemaVersion = "db.version"
24 24
 	// latest schema of the db
25
-	latestDbSchema = "6"
25
+	latestDbSchema = "7"
26 26
 )
27 27
 
28 28
 type SchemaChanger func(*Config, *buntdb.Tx) error
@@ -440,6 +440,66 @@ func schemaChangeV5ToV6(config *Config, tx *buntdb.Tx) error {
440 440
 	return nil
441 441
 }
442 442
 
443
+type maskInfoV7 struct {
444
+	TimeCreated     time.Time
445
+	CreatorNickmask string
446
+	CreatorAccount  string
447
+}
448
+
449
+func schemaChangeV6ToV7(config *Config, tx *buntdb.Tx) error {
450
+	now := time.Now().UTC()
451
+	var channels []string
452
+	prefix := "channel.exists "
453
+	tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
454
+		if !strings.HasPrefix(key, prefix) {
455
+			return false
456
+		}
457
+		channels = append(channels, strings.TrimPrefix(key, prefix))
458
+		return true
459
+	})
460
+
461
+	converter := func(key string) {
462
+		oldRawValue, err := tx.Get(key)
463
+		if err != nil {
464
+			return
465
+		}
466
+		var masks []string
467
+		err = json.Unmarshal([]byte(oldRawValue), &masks)
468
+		if err != nil {
469
+			return
470
+		}
471
+		newCookedValue := make(map[string]maskInfoV7)
472
+		for _, mask := range masks {
473
+			normalizedMask, err := CanonicalizeMaskWildcard(mask)
474
+			if err != nil {
475
+				continue
476
+			}
477
+			newCookedValue[normalizedMask] = maskInfoV7{
478
+				TimeCreated:     now,
479
+				CreatorNickmask: "*",
480
+				CreatorAccount:  "*",
481
+			}
482
+		}
483
+		newRawValue, err := json.Marshal(newCookedValue)
484
+		if err != nil {
485
+			return
486
+		}
487
+		tx.Set(key, string(newRawValue), nil)
488
+	}
489
+
490
+	prefixes := []string{
491
+		"channel.banlist %s",
492
+		"channel.exceptlist %s",
493
+		"channel.invitelist %s",
494
+	}
495
+	for _, channel := range channels {
496
+		for _, prefix := range prefixes {
497
+			converter(fmt.Sprintf(prefix, channel))
498
+		}
499
+	}
500
+	return nil
501
+}
502
+
443 503
 func init() {
444 504
 	allChanges := []SchemaChange{
445 505
 		{
@@ -467,6 +527,11 @@ func init() {
467 527
 			TargetVersion:  "6",
468 528
 			Changer:        schemaChangeV5ToV6,
469 529
 		},
530
+		{
531
+			InitialVersion: "6",
532
+			TargetVersion:  "7",
533
+			Changer:        schemaChangeV6ToV7,
534
+		},
470 535
 	}
471 536
 
472 537
 	// build the index

+ 1
- 1
irc/handlers.go View File

@@ -1738,7 +1738,7 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
1738 1738
 	} else {
1739 1739
 		args := append([]string{client.nick, channel.name}, channel.modeStrings(client)...)
1740 1740
 		rb.Add(nil, prefix, RPL_CHANNELMODEIS, args...)
1741
-		rb.Add(nil, client.nickMaskString, RPL_CHANNELCREATED, client.nick, channel.name, strconv.FormatInt(channel.createdTime.Unix(), 10))
1741
+		rb.Add(nil, client.nickMaskString, RPL_CREATIONTIME, client.nick, channel.name, strconv.FormatInt(channel.createdTime.Unix(), 10))
1742 1742
 	}
1743 1743
 	return false
1744 1744
 }

+ 8
- 10
irc/modes.go View File

@@ -158,12 +158,7 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c
158 158
 				continue
159 159
 			}
160 160
 
161
-			// confirm mask looks valid
162
-			mask, err := Casefold(change.Arg)
163
-			if err != nil {
164
-				continue
165
-			}
166
-
161
+			mask := change.Arg
167 162
 			switch change.Op {
168 163
 			case modes.Add:
169 164
 				if channel.lists[change.Mode].Length() >= client.server.Config().Limits.ChanListModes {
@@ -174,12 +169,15 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c
174 169
 					continue
175 170
 				}
176 171
 
177
-				channel.lists[change.Mode].Add(mask)
178
-				applied = append(applied, change)
172
+				details := client.Details()
173
+				if success := channel.lists[change.Mode].Add(mask, details.nickMask, details.accountName); success {
174
+					applied = append(applied, change)
175
+				}
179 176
 
180 177
 			case modes.Remove:
181
-				channel.lists[change.Mode].Remove(mask)
182
-				applied = append(applied, change)
178
+				if success := channel.lists[change.Mode].Remove(mask); success {
179
+					applied = append(applied, change)
180
+				}
183 181
 			}
184 182
 
185 183
 		case modes.UserLimit:

+ 1
- 1
irc/numerics.go View File

@@ -70,7 +70,7 @@ const (
70 70
 	RPL_LISTEND                   = "323"
71 71
 	RPL_CHANNELMODEIS             = "324"
72 72
 	RPL_UNIQOPIS                  = "325"
73
-	RPL_CHANNELCREATED            = "329"
73
+	RPL_CREATIONTIME              = "329"
74 74
 	RPL_WHOISACCOUNT              = "330"
75 75
 	RPL_NOTOPIC                   = "331"
76 76
 	RPL_TOPIC                     = "332"

+ 19
- 3
irc/strings.go View File

@@ -8,6 +8,7 @@ package irc
8 8
 import (
9 9
 	"fmt"
10 10
 	"strings"
11
+	"unicode"
11 12
 
12 13
 	"github.com/oragono/confusables"
13 14
 	"golang.org/x/text/cases"
@@ -191,9 +192,15 @@ func CanonicalizeMaskWildcard(userhost string) (expanded string, err error) {
191 192
 		nick = "*"
192 193
 	}
193 194
 	if nick != "*" {
194
-		nick, err = Casefold(nick)
195
-		if err != nil {
196
-			return "", err
195
+		// XXX allow nick wildcards in pure ASCII, but not in unicode,
196
+		// because the * character breaks casefolding
197
+		if IsPureASCII(nick) {
198
+			nick = strings.ToLower(nick)
199
+		} else {
200
+			nick, err = Casefold(nick)
201
+			if err != nil {
202
+				return "", err
203
+			}
197 204
 		}
198 205
 	}
199 206
 	if user == "" {
@@ -210,3 +217,12 @@ func CanonicalizeMaskWildcard(userhost string) (expanded string, err error) {
210 217
 	}
211 218
 	return fmt.Sprintf("%s!%s@%s", nick, user, host), nil
212 219
 }
220
+
221
+func IsPureASCII(str string) bool {
222
+	for i := 0; i < len(str); i++ {
223
+		if unicode.MaxASCII < str[i] {
224
+			return false
225
+		}
226
+	}
227
+	return true
228
+}

+ 1
- 0
irc/strings_test.go View File

@@ -211,4 +211,5 @@ func TestCanonicalizeMaskWildcard(t *testing.T) {
211 211
 	tester("slingamn!shivaram*", "slingamn!shivaram*@*", nil)
212 212
 	tester("slingamn!", "slingamn!*@*", nil)
213 213
 	tester("shivaram*@good-fortune", "*!shivaram*@good-fortune", nil)
214
+	tester("shivaram*", "shivaram*!*@*", nil)
214 215
 }

Loading…
Cancel
Save