This commit is contained in:
Shivaram Lingamneni
2020-05-13 03:41:00 -04:00
parent 21958768d8
commit be0dedf260
12 changed files with 199 additions and 437 deletions
+19 -27
View File
@@ -7,14 +7,12 @@ package irc
import (
"regexp"
"strings"
"sync"
"time"
"github.com/goshuirc/irc-go/ircmatch"
"github.com/oragono/oragono/irc/caps"
"github.com/oragono/oragono/irc/modes"
"sync"
"github.com/oragono/oragono/irc/utils"
)
// ClientManager keeps track of clients by nick, enforcing uniqueness of casefolded nicks
@@ -301,12 +299,16 @@ func (clients *ClientManager) FindAll(userhost string) (set ClientSet) {
if err != nil {
return set
}
matcher := ircmatch.MakeMatch(userhost)
matcher, err := utils.CompileGlob(userhost, false)
if err != nil {
// not much we can do here
return
}
clients.RLock()
defer clients.RUnlock()
for _, client := range clients.byNick {
if matcher.Match(client.NickMaskCasefolded()) {
if matcher.MatchString(client.NickMaskCasefolded()) {
set.Add(client)
}
}
@@ -330,8 +332,9 @@ type MaskInfo struct {
// UserMaskSet holds a set of client masks and lets you match hostnames to them.
type UserMaskSet struct {
sync.RWMutex
masks map[string]MaskInfo
regexp *regexp.Regexp
serialCacheUpdateMutex sync.Mutex
masks map[string]MaskInfo
regexp *regexp.Regexp
}
func NewUserMaskSet() *UserMaskSet {
@@ -345,6 +348,9 @@ func (set *UserMaskSet) Add(mask, creatorNickmask, creatorAccount string) (maskA
return
}
set.serialCacheUpdateMutex.Lock()
defer set.serialCacheUpdateMutex.Unlock()
set.Lock()
if set.masks == nil {
set.masks = make(map[string]MaskInfo)
@@ -373,6 +379,9 @@ func (set *UserMaskSet) Remove(mask string) (maskRemoved string, err error) {
return
}
set.serialCacheUpdateMutex.Lock()
defer set.serialCacheUpdateMutex.Unlock()
set.Lock()
_, removed := set.masks[mask]
if removed {
@@ -430,31 +439,14 @@ func (set *UserMaskSet) Length() int {
// parts are re-joined and finally all masks are joined into a big
// or-expression.
func (set *UserMaskSet) setRegexp() {
var re *regexp.Regexp
set.RLock()
maskExprs := make([]string, len(set.masks))
index := 0
for mask := range set.masks {
manyParts := strings.Split(mask, "*")
manyExprs := make([]string, len(manyParts))
for mindex, manyPart := range manyParts {
oneParts := strings.Split(manyPart, "?")
oneExprs := make([]string, len(oneParts))
for oindex, onePart := range oneParts {
oneExprs[oindex] = regexp.QuoteMeta(onePart)
}
manyExprs[mindex] = strings.Join(oneExprs, ".")
}
maskExprs[index] = strings.Join(manyExprs, ".*")
index++
maskExprs = append(maskExprs, mask)
}
set.RUnlock()
if index > 0 {
expr := "^(" + strings.Join(maskExprs, "|") + ")$"
re, _ = regexp.Compile(expr)
}
re, _ := utils.CompileMasks(maskExprs)
set.Lock()
set.regexp = re
+3 -3
View File
@@ -853,7 +853,7 @@ func LoadConfig(filename string) (config *Config, err error) {
}
for _, glob := range config.Server.WebSockets.AllowedOrigins {
globre, err := utils.CompileGlob(glob)
globre, err := utils.CompileGlob(glob, false)
if err != nil {
return nil, fmt.Errorf("invalid websocket allowed-origin expression: %s", glob)
}
@@ -1219,7 +1219,7 @@ func compileGuestRegexp(guestFormat string, casemapping Casemapping) (standard,
return
}
standard, err = utils.CompileGlob(guestFormat)
standard, err = utils.CompileGlob(guestFormat, true)
if err != nil {
return
}
@@ -1235,6 +1235,6 @@ func compileGuestRegexp(guestFormat string, casemapping Casemapping) (standard,
if err != nil {
return
}
folded, err = utils.CompileGlob(fmt.Sprintf("%s*%s", initialFolded, finalFolded))
folded, err = utils.CompileGlob(fmt.Sprintf("%s*%s", initialFolded, finalFolded), false)
return
}
+7 -4
View File
@@ -21,7 +21,6 @@ import (
"time"
"github.com/goshuirc/irc-go/ircfmt"
"github.com/goshuirc/irc-go/ircmatch"
"github.com/goshuirc/irc-go/ircmsg"
"github.com/oragono/oragono/irc/caps"
"github.com/oragono/oragono/irc/custime"
@@ -1280,10 +1279,14 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
return false
}
matcher := ircmatch.MakeMatch(mask)
matcher, err := utils.CompileGlob(mask, false)
if err != nil {
rb.Add(nil, server.name, ERR_UNKNOWNERROR, details.nick, msg.Command, client.t("Erroneous nickname"))
return false
}
for _, clientMask := range client.AllNickmasks() {
if !klineMyself && matcher.Match(clientMask) {
if !klineMyself && matcher.MatchString(clientMask) {
rb.Add(nil, server.name, ERR_UNKNOWNERROR, details.nick, msg.Command, client.t("This ban matches you. To KLINE yourself, you must use the command: /KLINE MYSELF <arguments>"))
return false
}
@@ -1327,7 +1330,7 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
for _, mcl := range server.clients.AllClients() {
for _, clientMask := range mcl.AllNickmasks() {
if matcher.Match(clientMask) {
if matcher.MatchString(clientMask) {
clientsToKill = append(clientsToKill, mcl)
killedClientNicks = append(killedClientNicks, mcl.nick)
}
+11 -4
View File
@@ -6,12 +6,14 @@ package irc
import (
"encoding/json"
"fmt"
"regexp"
"strings"
"sync"
"time"
"github.com/goshuirc/irc-go/ircmatch"
"github.com/tidwall/buntdb"
"github.com/oragono/oragono/irc/utils"
)
const (
@@ -23,7 +25,7 @@ type KLineInfo struct {
// Mask that is blocked.
Mask string
// Matcher, to facilitate fast matching.
Matcher ircmatch.Matcher
Matcher *regexp.Regexp
// Info contains information on the ban.
Info IPBanInfo
}
@@ -80,9 +82,14 @@ func (km *KLineManager) AddMask(mask string, duration time.Duration, reason, ope
}
func (km *KLineManager) addMaskInternal(mask string, info IPBanInfo) {
re, err := utils.CompileGlob(mask, false)
// this is validated externally and shouldn't fail regardless
if err != nil {
return
}
kln := KLineInfo{
Mask: mask,
Matcher: ircmatch.MakeMatch(mask),
Matcher: re,
Info: info,
}
@@ -189,7 +196,7 @@ func (km *KLineManager) CheckMasks(masks ...string) (isBanned bool, info IPBanIn
for _, entryInfo := range km.entries {
for _, mask := range masks {
if entryInfo.Matcher.Match(mask) {
if entryInfo.Matcher.MatchString(mask) {
return true, entryInfo.Info
}
}
+38 -6
View File
@@ -11,21 +11,53 @@ import (
// yet another glob implementation in Go
func CompileGlob(glob string) (result *regexp.Regexp, err error) {
var buf bytes.Buffer
buf.WriteByte('^')
func addRegexp(buf *bytes.Buffer, glob string, submatch bool) (err error) {
for _, r := range glob {
switch r {
case '*':
buf.WriteString("(.*)")
if submatch {
buf.WriteString("(.*)")
} else {
buf.WriteString(".*")
}
case '?':
buf.WriteString("(.)")
if submatch {
buf.WriteString("(.)")
} else {
buf.WriteString(".")
}
case 0xFFFD:
return nil, &syntax.Error{Code: syntax.ErrInvalidUTF8, Expr: glob}
return &syntax.Error{Code: syntax.ErrInvalidUTF8, Expr: glob}
default:
buf.WriteString(regexp.QuoteMeta(string(r)))
}
}
return
}
func CompileGlob(glob string, submatch bool) (result *regexp.Regexp, err error) {
var buf bytes.Buffer
buf.WriteByte('^')
err = addRegexp(&buf, glob, submatch)
if err != nil {
return
}
buf.WriteByte('$')
return regexp.Compile(buf.String())
}
func CompileMasks(masks []string) (result *regexp.Regexp, err error) {
var buf bytes.Buffer
buf.WriteString("^(")
for i, mask := range masks {
err = addRegexp(&buf, mask, false)
if err != nil {
return
}
if i != len(masks)-1 {
buf.WriteByte('|')
}
}
buf.WriteString(")$")
return regexp.Compile(buf.String())
}
+121 -1
View File
@@ -9,7 +9,7 @@ import (
)
func globMustCompile(glob string) *regexp.Regexp {
re, err := CompileGlob(glob)
re, err := CompileGlob(glob, false)
if err != nil {
panic(err)
}
@@ -46,3 +46,123 @@ func TestGlob(t *testing.T) {
assertMatches("S*e", "Skåne", true, t)
assertMatches("Sk?ne", "Skåne", true, t)
}
func BenchmarkGlob(b *testing.B) {
g := globMustCompile("https://*google.com")
b.ResetTimer()
for i := 0; i < b.N; i++ {
g.MatchString("https://www.google.com")
}
}
func BenchmarkGlobCompilation(b *testing.B) {
for i := 0; i < b.N; i++ {
CompileGlob("https://*google.com", false)
}
}
// these are actual bans from my production network :-/
var bans = []string{
"*!*@tor-network.onion",
"`!*@*",
"qanon!*@*",
"*!bibi@tor-network.onion",
"shivarm!*@*",
"8====d!*@*",
"shiviram!*@*",
"poop*!*@*",
"shivoram!*@*",
"shivvy!*@*",
"shavirim!*@*",
"shivarm_!*@*",
"_!*@*",
}
func TestMasks(t *testing.T) {
matcher, err := CompileMasks(bans)
if err != nil {
panic(err)
}
if !matcher.MatchString("evan!user@tor-network.onion") {
t.Errorf("match expected")
}
if !matcher.MatchString("`!evan@b9un4fv3he44q.example.com") {
t.Errorf("match expected")
}
if matcher.MatchString("horse!horse@t5dwi8vacg47y.example.com") {
t.Errorf("match not expected")
}
if matcher.MatchString("horse_!horse@t5dwi8vacg47y.example.com") {
t.Errorf("match not expected")
}
if matcher.MatchString("shivaram!shivaram@yrqgsrjy2p7my.example.com") {
t.Errorf("match not expected")
}
}
func BenchmarkMasksCompile(b *testing.B) {
for i := 0; i < b.N; i++ {
CompileMasks(bans)
}
}
func BenchmarkMasksMatch(b *testing.B) {
matcher, _ := CompileMasks(bans)
b.ResetTimer()
for i := 0; i < b.N; i++ {
matcher.MatchString("evan!user@tor-network.onion")
matcher.MatchString("horse_!horse@t5dwi8vacg47y.example.com")
matcher.MatchString("shivaram!shivaram@yrqgsrjy2p7my.example.com")
}
}
// compare performance to compilation of the | clauses as separate regexes
// first for compilation, then for matching
func compileAll(masks []string) (result []*regexp.Regexp, err error) {
a := make([]*regexp.Regexp, 0, len(masks))
for _, mask := range masks {
m, err := CompileGlob(mask, false)
if err != nil {
return nil, err
}
a = append(a, m)
}
return a, nil
}
func matchesAny(masks []*regexp.Regexp, str string) bool {
for _, r := range masks {
if r.MatchString(str) {
return true
}
}
return false
}
func BenchmarkLinearCompile(b *testing.B) {
for i := 0; i < b.N; i++ {
compileAll(bans)
}
}
func BenchmarkLinearMatch(b *testing.B) {
a, err := compileAll(bans)
if err != nil {
panic(err)
}
if matchesAny(a, "horse_!horse@t5dwi8vacg47y.example.com") {
panic("incorrect match")
}
if !matchesAny(a, "evan!user@tor-network.onion") {
panic("incorrect match")
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
matchesAny(a, "horse_!horse@t5dwi8vacg47y.example.com")
matchesAny(a, "evan!user@tor-network.onion")
matchesAny(a, "shivaram!shivaram@yrqgsrjy2p7my.example.com")
}
}