mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-22 11:59:40 +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.
|
// 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) {
|
func (channel *Channel) CanSpeak(client *Client) (bool, modes.Mode) {
|
||||||
channel.stateMutex.RLock()
|
channel.stateMutex.RLock()
|
||||||
defer channel.stateMutex.RUnlock()
|
clientModes, hasClient := channel.members[client]
|
||||||
|
channel.stateMutex.RUnlock()
|
||||||
|
|
||||||
_, hasClient := channel.members[client]
|
if !hasClient && channel.flags.HasMode(modes.NoOutside) {
|
||||||
if channel.flags.HasMode(modes.NoOutside) && !hasClient {
|
// TODO: enforce regular +b bans on -n channels?
|
||||||
return false, modes.NoOutside
|
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
|
return false, modes.Moderated
|
||||||
}
|
}
|
||||||
if channel.flags.HasMode(modes.RegisteredOnly) && client.Account() == "" {
|
if channel.flags.HasMode(modes.RegisteredOnly) && client.Account() == "" {
|
||||||
return false, modes.RegisteredOnly
|
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 false, modes.RegisteredOnlySpeak
|
||||||
}
|
}
|
||||||
return true, modes.Mode('?')
|
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) {
|
func msgCommandToHistType(command string) (history.ItemType, error) {
|
||||||
switch command {
|
switch command {
|
||||||
case "PRIVMSG":
|
case "PRIVMSG":
|
||||||
|
@ -5,10 +5,8 @@
|
|||||||
package irc
|
package irc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/oragono/oragono/irc/caps"
|
"github.com/oragono/oragono/irc/caps"
|
||||||
"github.com/oragono/oragono/irc/modes"
|
"github.com/oragono/oragono/irc/modes"
|
||||||
@ -306,134 +304,3 @@ func (clients *ClientManager) FindAll(userhost string) (set ClientSet) {
|
|||||||
|
|
||||||
return set
|
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 {
|
if config.Extjwt.Default.Enabled() || len(config.Extjwt.Services) != 0 {
|
||||||
isupport.Add("EXTJWT", "1")
|
isupport.Add("EXTJWT", "1")
|
||||||
}
|
}
|
||||||
|
isupport.Add("EXTBAN", ",m")
|
||||||
isupport.Add("INVEX", "")
|
isupport.Add("INVEX", "")
|
||||||
isupport.Add("KICKLEN", strconv.Itoa(config.Limits.KickLen))
|
isupport.Add("KICKLEN", strconv.Itoa(config.Limits.KickLen))
|
||||||
isupport.Add("MAXLIST", fmt.Sprintf("beI:%s", strconv.Itoa(config.Limits.ChanListModes)))
|
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…
Reference in New Issue
Block a user