mirror of
				https://github.com/ergochat/ergo.git
				synced 2025-10-31 13:57:23 +01:00 
			
		
		
		
	fix #307
This commit is contained in:
		
							parent
							
								
									50dc265e4d
								
							
						
					
					
						commit
						bd40b46639
					
				| @ -1227,24 +1227,38 @@ func (channel *Channel) SetTopic(client *Client, topic string, rb *ResponseBuffe | ||||
| // 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. | ||||
| func (channel *Channel) CanSpeak(client *Client) (bool, modes.Mode) { | ||||
| 	channel.stateMutex.RLock() | ||||
| 	defer channel.stateMutex.RUnlock() | ||||
| 	clientModes, hasClient := channel.members[client] | ||||
| 	channel.stateMutex.RUnlock() | ||||
| 
 | ||||
| 	_, hasClient := channel.members[client] | ||||
| 	if channel.flags.HasMode(modes.NoOutside) && !hasClient { | ||||
| 	if !hasClient && channel.flags.HasMode(modes.NoOutside) { | ||||
| 		// TODO: enforce regular +b bans on -n channels? | ||||
| 		return false, modes.NoOutside | ||||
| 	} | ||||
| 	if channel.flags.HasMode(modes.Moderated) && !channel.ClientIsAtLeast(client, modes.Voice) { | ||||
| 	if channel.isMuted(client) && clientModes.HighestChannelUserMode() == modes.Mode(0) { | ||||
| 		return false, modes.BanMask | ||||
| 	} | ||||
| 	if channel.flags.HasMode(modes.Moderated) && clientModes.HighestChannelUserMode() == modes.Mode(0) { | ||||
| 		return false, modes.Moderated | ||||
| 	} | ||||
| 	if channel.flags.HasMode(modes.RegisteredOnly) && client.Account() == "" { | ||||
| 		return false, modes.RegisteredOnly | ||||
| 	} | ||||
| 	if channel.flags.HasMode(modes.RegisteredOnlySpeak) && client.Account() == "" && !channel.ClientIsAtLeast(client, modes.Voice) { | ||||
| 	if channel.flags.HasMode(modes.RegisteredOnlySpeak) && client.Account() == "" && | ||||
| 		clientModes.HighestChannelUserMode() != modes.Mode(0) { | ||||
| 		return false, modes.RegisteredOnlySpeak | ||||
| 	} | ||||
| 	return true, modes.Mode('?') | ||||
| } | ||||
| 
 | ||||
| func (channel *Channel) isMuted(client *Client) bool { | ||||
| 	muteRe := channel.lists[modes.BanMask].MuteRegexp() | ||||
| 	if muteRe == nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	nuh := client.NickMaskString() | ||||
| 	return muteRe.MatchString(nuh) && !channel.lists[modes.ExceptMask].MatchMute(nuh) | ||||
| } | ||||
| 
 | ||||
| func msgCommandToHistType(command string) (history.ItemType, error) { | ||||
| 	switch command { | ||||
| 	case "PRIVMSG": | ||||
|  | ||||
| @ -5,10 +5,8 @@ | ||||
| package irc | ||||
| 
 | ||||
| import ( | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/oragono/oragono/irc/caps" | ||||
| 	"github.com/oragono/oragono/irc/modes" | ||||
| @ -306,134 +304,3 @@ func (clients *ClientManager) FindAll(userhost string) (set ClientSet) { | ||||
| 
 | ||||
| 	return set | ||||
| } | ||||
| 
 | ||||
| // | ||||
| // usermask to regexp | ||||
| // | ||||
| 
 | ||||
| //TODO(dan): move this over to generally using glob syntax instead? | ||||
| // kinda more expected in normal ban/etc masks, though regex is useful (probably as an extban?) | ||||
| 
 | ||||
| type MaskInfo struct { | ||||
| 	TimeCreated     time.Time | ||||
| 	CreatorNickmask string | ||||
| 	CreatorAccount  string | ||||
| } | ||||
| 
 | ||||
| // UserMaskSet holds a set of client masks and lets you match  hostnames to them. | ||||
| type UserMaskSet struct { | ||||
| 	sync.RWMutex | ||||
| 	serialCacheUpdateMutex sync.Mutex | ||||
| 	masks                  map[string]MaskInfo | ||||
| 	regexp                 *regexp.Regexp | ||||
| } | ||||
| 
 | ||||
| func NewUserMaskSet() *UserMaskSet { | ||||
| 	return new(UserMaskSet) | ||||
| } | ||||
| 
 | ||||
| // Add adds the given mask to this set. | ||||
| func (set *UserMaskSet) Add(mask, creatorNickmask, creatorAccount string) (maskAdded string, err error) { | ||||
| 	casefoldedMask, err := CanonicalizeMaskWildcard(mask) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	set.serialCacheUpdateMutex.Lock() | ||||
| 	defer set.serialCacheUpdateMutex.Unlock() | ||||
| 
 | ||||
| 	set.Lock() | ||||
| 	if set.masks == nil { | ||||
| 		set.masks = make(map[string]MaskInfo) | ||||
| 	} | ||||
| 	_, present := set.masks[casefoldedMask] | ||||
| 	if !present { | ||||
| 		maskAdded = casefoldedMask | ||||
| 		set.masks[casefoldedMask] = MaskInfo{ | ||||
| 			TimeCreated:     time.Now().UTC(), | ||||
| 			CreatorNickmask: creatorNickmask, | ||||
| 			CreatorAccount:  creatorAccount, | ||||
| 		} | ||||
| 	} | ||||
| 	set.Unlock() | ||||
| 
 | ||||
| 	if !present { | ||||
| 		set.setRegexp() | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Remove removes the given mask from this set. | ||||
| func (set *UserMaskSet) Remove(mask string) (maskRemoved string, err error) { | ||||
| 	mask, err = CanonicalizeMaskWildcard(mask) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	set.serialCacheUpdateMutex.Lock() | ||||
| 	defer set.serialCacheUpdateMutex.Unlock() | ||||
| 
 | ||||
| 	set.Lock() | ||||
| 	_, removed := set.masks[mask] | ||||
| 	if removed { | ||||
| 		maskRemoved = mask | ||||
| 		delete(set.masks, mask) | ||||
| 	} | ||||
| 	set.Unlock() | ||||
| 
 | ||||
| 	if removed { | ||||
| 		set.setRegexp() | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (set *UserMaskSet) SetMasks(masks map[string]MaskInfo) { | ||||
| 	set.Lock() | ||||
| 	set.masks = masks | ||||
| 	set.Unlock() | ||||
| 	set.setRegexp() | ||||
| } | ||||
| 
 | ||||
| func (set *UserMaskSet) Masks() (result map[string]MaskInfo) { | ||||
| 	set.RLock() | ||||
| 	defer set.RUnlock() | ||||
| 
 | ||||
| 	result = make(map[string]MaskInfo, len(set.masks)) | ||||
| 	for mask, info := range set.masks { | ||||
| 		result[mask] = info | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Match matches the given n!u@h. | ||||
| func (set *UserMaskSet) Match(userhost string) bool { | ||||
| 	set.RLock() | ||||
| 	regexp := set.regexp | ||||
| 	set.RUnlock() | ||||
| 
 | ||||
| 	if regexp == nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	return regexp.MatchString(userhost) | ||||
| } | ||||
| 
 | ||||
| func (set *UserMaskSet) Length() int { | ||||
| 	set.RLock() | ||||
| 	defer set.RUnlock() | ||||
| 	return len(set.masks) | ||||
| } | ||||
| 
 | ||||
| func (set *UserMaskSet) setRegexp() { | ||||
| 	set.RLock() | ||||
| 	maskExprs := make([]string, len(set.masks)) | ||||
| 	for mask := range set.masks { | ||||
| 		maskExprs = append(maskExprs, mask) | ||||
| 	} | ||||
| 	set.RUnlock() | ||||
| 
 | ||||
| 	re, _ := utils.CompileMasks(maskExprs) | ||||
| 
 | ||||
| 	set.Lock() | ||||
| 	set.regexp = re | ||||
| 	set.Unlock() | ||||
| } | ||||
|  | ||||
| @ -1298,6 +1298,7 @@ func (config *Config) generateISupport() (err error) { | ||||
| 	if config.Extjwt.Default.Enabled() || len(config.Extjwt.Services) != 0 { | ||||
| 		isupport.Add("EXTJWT", "1") | ||||
| 	} | ||||
| 	isupport.Add("EXTBAN", ",m") | ||||
| 	isupport.Add("INVEX", "") | ||||
| 	isupport.Add("KICKLEN", strconv.Itoa(config.Limits.KickLen)) | ||||
| 	isupport.Add("MAXLIST", fmt.Sprintf("beI:%s", strconv.Itoa(config.Limits.ChanListModes))) | ||||
|  | ||||
							
								
								
									
										167
									
								
								irc/usermaskset.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								irc/usermaskset.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,167 @@ | ||||
| // Copyright (c) 2012-2014 Jeremy Latt | ||||
| // Copyright (c) 2016-2018 Daniel Oaks | ||||
| // Copyright (c) 2019-2020 Shivaram Lingamneni | ||||
| // released under the MIT license | ||||
| 
 | ||||
| package irc | ||||
| 
 | ||||
| import ( | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"time" | ||||
| 	"unsafe" | ||||
| 
 | ||||
| 	"github.com/oragono/oragono/irc/utils" | ||||
| ) | ||||
| 
 | ||||
| type MaskInfo struct { | ||||
| 	TimeCreated     time.Time | ||||
| 	CreatorNickmask string | ||||
| 	CreatorAccount  string | ||||
| } | ||||
| 
 | ||||
| // UserMaskSet holds a set of client masks and lets you match  hostnames to them. | ||||
| type UserMaskSet struct { | ||||
| 	sync.RWMutex | ||||
| 	serialCacheUpdateMutex sync.Mutex | ||||
| 	masks                  map[string]MaskInfo | ||||
| 	regexp                 unsafe.Pointer | ||||
| 	muteRegexp             unsafe.Pointer | ||||
| } | ||||
| 
 | ||||
| func NewUserMaskSet() *UserMaskSet { | ||||
| 	return new(UserMaskSet) | ||||
| } | ||||
| 
 | ||||
| // Add adds the given mask to this set. | ||||
| func (set *UserMaskSet) Add(mask, creatorNickmask, creatorAccount string) (maskAdded string, err error) { | ||||
| 	casefoldedMask, err := CanonicalizeMaskWildcard(mask) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	set.serialCacheUpdateMutex.Lock() | ||||
| 	defer set.serialCacheUpdateMutex.Unlock() | ||||
| 
 | ||||
| 	set.Lock() | ||||
| 	if set.masks == nil { | ||||
| 		set.masks = make(map[string]MaskInfo) | ||||
| 	} | ||||
| 	_, present := set.masks[casefoldedMask] | ||||
| 	if !present { | ||||
| 		maskAdded = casefoldedMask | ||||
| 		set.masks[casefoldedMask] = MaskInfo{ | ||||
| 			TimeCreated:     time.Now().UTC(), | ||||
| 			CreatorNickmask: creatorNickmask, | ||||
| 			CreatorAccount:  creatorAccount, | ||||
| 		} | ||||
| 	} | ||||
| 	set.Unlock() | ||||
| 
 | ||||
| 	if !present { | ||||
| 		set.setRegexp() | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Remove removes the given mask from this set. | ||||
| func (set *UserMaskSet) Remove(mask string) (maskRemoved string, err error) { | ||||
| 	mask, err = CanonicalizeMaskWildcard(mask) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	set.serialCacheUpdateMutex.Lock() | ||||
| 	defer set.serialCacheUpdateMutex.Unlock() | ||||
| 
 | ||||
| 	set.Lock() | ||||
| 	_, removed := set.masks[mask] | ||||
| 	if removed { | ||||
| 		maskRemoved = mask | ||||
| 		delete(set.masks, mask) | ||||
| 	} | ||||
| 	set.Unlock() | ||||
| 
 | ||||
| 	if removed { | ||||
| 		set.setRegexp() | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (set *UserMaskSet) SetMasks(masks map[string]MaskInfo) { | ||||
| 	set.Lock() | ||||
| 	set.masks = masks | ||||
| 	set.Unlock() | ||||
| 	set.setRegexp() | ||||
| } | ||||
| 
 | ||||
| func (set *UserMaskSet) Masks() (result map[string]MaskInfo) { | ||||
| 	set.RLock() | ||||
| 	defer set.RUnlock() | ||||
| 
 | ||||
| 	result = make(map[string]MaskInfo, len(set.masks)) | ||||
| 	for mask, info := range set.masks { | ||||
| 		result[mask] = info | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Match matches the given n!u@h against the standard (non-ext) bans. | ||||
| func (set *UserMaskSet) Match(userhost string) bool { | ||||
| 	regexp := (*regexp.Regexp)(atomic.LoadPointer(&set.regexp)) | ||||
| 
 | ||||
| 	if regexp == nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	return regexp.MatchString(userhost) | ||||
| } | ||||
| 
 | ||||
| // MatchMute matches the given NUH against the mute extbans. | ||||
| func (set *UserMaskSet) MatchMute(userhost string) bool { | ||||
| 	regexp := set.MuteRegexp() | ||||
| 
 | ||||
| 	if regexp == nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	return regexp.MatchString(userhost) | ||||
| } | ||||
| 
 | ||||
| func (set *UserMaskSet) MuteRegexp() *regexp.Regexp { | ||||
| 	return (*regexp.Regexp)(atomic.LoadPointer(&set.muteRegexp)) | ||||
| } | ||||
| 
 | ||||
| func (set *UserMaskSet) Length() int { | ||||
| 	set.RLock() | ||||
| 	defer set.RUnlock() | ||||
| 	return len(set.masks) | ||||
| } | ||||
| 
 | ||||
| func (set *UserMaskSet) setRegexp() { | ||||
| 	set.RLock() | ||||
| 	maskExprs := make([]string, 0, len(set.masks)) | ||||
| 	var muteExprs []string | ||||
| 	for mask := range set.masks { | ||||
| 		if strings.HasPrefix(mask, "m:") { | ||||
| 			muteExprs = append(muteExprs, mask[2:]) | ||||
| 		} else { | ||||
| 			maskExprs = append(maskExprs, mask) | ||||
| 		} | ||||
| 	} | ||||
| 	set.RUnlock() | ||||
| 
 | ||||
| 	compileMasks := func(masks []string) *regexp.Regexp { | ||||
| 		if len(masks) == 0 { | ||||
| 			return nil | ||||
| 		} | ||||
| 		re, _ := utils.CompileMasks(masks) | ||||
| 		return re | ||||
| 	} | ||||
| 
 | ||||
| 	re := compileMasks(maskExprs) | ||||
| 	muteRe := compileMasks(muteExprs) | ||||
| 
 | ||||
| 	atomic.StorePointer(&set.regexp, unsafe.Pointer(re)) | ||||
| 	atomic.StorePointer(&set.muteRegexp, unsafe.Pointer(muteRe)) | ||||
| } | ||||
							
								
								
									
										36
									
								
								irc/usermaskset_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								irc/usermaskset_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| // Copyright (c) 2020 Shivaram Lingamneni | ||||
| // released under the MIT license | ||||
| 
 | ||||
| package irc | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| func TestUserMaskSet(t *testing.T) { | ||||
| 	s := NewUserMaskSet() | ||||
| 
 | ||||
| 	if s.Match("horse!~evan@tor-network.onion") { | ||||
| 		t.Errorf("empty set should not match anything") | ||||
| 	} | ||||
| 
 | ||||
| 	s.Add("m:horse!*@*", "", "") | ||||
| 	if s.Match("horse!~evan@tor-network.onion") { | ||||
| 		t.Errorf("mute extbans should not Match(), only MatchMute()") | ||||
| 	} | ||||
| 
 | ||||
| 	s.Add("*!~evan@*", "", "") | ||||
| 	if !s.Match("horse!~evan@tor-network.onion") { | ||||
| 		t.Errorf("expected Match() failed") | ||||
| 	} | ||||
| 	if s.Match("horse!~horse@tor-network.onion") { | ||||
| 		t.Errorf("unexpected Match() succeeded") | ||||
| 	} | ||||
| 
 | ||||
| 	if !s.MatchMute("horse!~evan@tor-network.onion") { | ||||
| 		t.Errorf("expected MatchMute() failed") | ||||
| 	} | ||||
| 	if s.MatchMute("evan!~evan@tor-network.onion") { | ||||
| 		t.Errorf("unexpected MatchMute() succeeded") | ||||
| 	} | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Shivaram Lingamneni
						Shivaram Lingamneni