This commit is contained in:
Shivaram Lingamneni 2019-07-14 16:17:37 -04:00
parent 07bb38ecdf
commit 938ce7e435
4 changed files with 100 additions and 36 deletions

View File

@ -18,20 +18,6 @@ import (
"sync" "sync"
) )
// ExpandUserHost takes a userhost, and returns an expanded version.
func ExpandUserHost(userhost string) (expanded string) {
expanded = userhost
// fill in missing wildcards for nicks
//TODO(dan): this would fail with dan@lol, fix that.
if !strings.Contains(expanded, "!") {
expanded += "!*"
}
if !strings.Contains(expanded, "@") {
expanded += "@*"
}
return
}
// ClientManager keeps track of clients by nick, enforcing uniqueness of casefolded nicks // ClientManager keeps track of clients by nick, enforcing uniqueness of casefolded nicks
type ClientManager struct { type ClientManager struct {
sync.RWMutex // tier 2 sync.RWMutex // tier 2
@ -241,7 +227,7 @@ func (clients *ClientManager) AllWithCapsNotify(capabs ...caps.Capability) (sess
func (clients *ClientManager) FindAll(userhost string) (set ClientSet) { func (clients *ClientManager) FindAll(userhost string) (set ClientSet) {
set = make(ClientSet) set = make(ClientSet)
userhost, err := Casefold(ExpandUserHost(userhost)) userhost, err := CanonicalizeMaskWildcard(userhost)
if err != nil { if err != nil {
return set return set
} }

View File

@ -1372,10 +1372,11 @@ func killHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
// KLINE [ANDKILL] [MYSELF] [duration] <mask> [ON <server>] [reason [| oper reason]] // KLINE [ANDKILL] [MYSELF] [duration] <mask> [ON <server>] [reason [| oper reason]]
// KLINE LIST // KLINE LIST
func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
details := client.Details()
// check oper permissions // check oper permissions
oper := client.Oper() oper := client.Oper()
if oper == nil || !oper.Class.Capabilities["oper:local_ban"] { if oper == nil || !oper.Class.Capabilities["oper:local_ban"] {
rb.Add(nil, server.name, ERR_NOPRIVS, client.nick, msg.Command, client.t("Insufficient oper privs")) rb.Add(nil, server.name, ERR_NOPRIVS, details.nick, msg.Command, client.t("Insufficient oper privs"))
return false return false
} }
@ -1421,31 +1422,31 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
// get mask // get mask
if len(msg.Params) < currentArg+1 { if len(msg.Params) < currentArg+1 {
rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, client.t("Not enough parameters")) rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, details.nick, msg.Command, client.t("Not enough parameters"))
return false return false
} }
mask := strings.ToLower(msg.Params[currentArg]) mask := msg.Params[currentArg]
currentArg++ currentArg++
// check mask // check mask
if !strings.Contains(mask, "!") && !strings.Contains(mask, "@") { mask, err = CanonicalizeMaskWildcard(mask)
mask = mask + "!*@*" if err != nil {
} else if !strings.Contains(mask, "@") { rb.Add(nil, server.name, ERR_UNKNOWNERROR, details.nick, msg.Command, client.t("Erroneous nickname"))
mask = mask + "@*" return false
} }
matcher := ircmatch.MakeMatch(mask) matcher := ircmatch.MakeMatch(mask)
for _, clientMask := range client.AllNickmasks() { for _, clientMask := range client.AllNickmasks() {
if !klineMyself && matcher.Match(clientMask) { if !klineMyself && matcher.Match(clientMask) {
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, client.t("This ban matches you. To KLINE yourself, you must use the command: /KLINE MYSELF <arguments>")) 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 return false
} }
} }
// check remote // check remote
if len(msg.Params) > currentArg && msg.Params[currentArg] == "ON" { if len(msg.Params) > currentArg && msg.Params[currentArg] == "ON" {
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, client.t("Remote servers not yet supported")) rb.Add(nil, server.name, ERR_UNKNOWNERROR, details.nick, msg.Command, client.t("Remote servers not yet supported"))
return false return false
} }
@ -1467,10 +1468,10 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
var snoDescription string var snoDescription string
if duration != 0 { if duration != 0 {
rb.Notice(fmt.Sprintf(client.t("Added temporary (%[1]s) K-Line for %[2]s"), duration.String(), mask)) rb.Notice(fmt.Sprintf(client.t("Added temporary (%[1]s) K-Line for %[2]s"), duration.String(), mask))
snoDescription = fmt.Sprintf(ircfmt.Unescape("%s [%s]$r added temporary (%s) K-Line for %s"), client.nick, operName, duration.String(), mask) snoDescription = fmt.Sprintf(ircfmt.Unescape("%s [%s]$r added temporary (%s) K-Line for %s"), details.nick, operName, duration.String(), mask)
} else { } else {
rb.Notice(fmt.Sprintf(client.t("Added K-Line for %s"), mask)) rb.Notice(fmt.Sprintf(client.t("Added K-Line for %s"), mask))
snoDescription = fmt.Sprintf(ircfmt.Unescape("%s [%s]$r added K-Line for %s"), client.nick, operName, mask) snoDescription = fmt.Sprintf(ircfmt.Unescape("%s [%s]$r added K-Line for %s"), details.nick, operName, mask)
} }
server.snomasks.Send(sno.LocalXline, snoDescription) server.snomasks.Send(sno.LocalXline, snoDescription)
@ -1501,7 +1502,7 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
// send snomask // send snomask
sort.Strings(killedClientNicks) sort.Strings(killedClientNicks)
server.snomasks.Send(sno.LocalKills, fmt.Sprintf(ircfmt.Unescape("%s [%s] killed %d clients with a KLINE $c[grey][$r%s$c[grey]]"), client.nick, operName, len(killedClientNicks), strings.Join(killedClientNicks, ", "))) server.snomasks.Send(sno.LocalKills, fmt.Sprintf(ircfmt.Unescape("%s [%s] killed %d clients with a KLINE $c[grey][$r%s$c[grey]]"), details.nick, operName, len(killedClientNicks), strings.Join(killedClientNicks, ", ")))
} }
return killClient return killClient
@ -2486,31 +2487,31 @@ func unDLineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
// UNKLINE <mask> // UNKLINE <mask>
func unKLineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { func unKLineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
details := client.Details()
// check oper permissions // check oper permissions
oper := client.Oper() oper := client.Oper()
if oper == nil || !oper.Class.Capabilities["oper:local_unban"] { if oper == nil || !oper.Class.Capabilities["oper:local_unban"] {
rb.Add(nil, server.name, ERR_NOPRIVS, client.nick, msg.Command, client.t("Insufficient oper privs")) rb.Add(nil, server.name, ERR_NOPRIVS, details.nick, msg.Command, client.t("Insufficient oper privs"))
return false return false
} }
// get host // get host
mask := msg.Params[0] mask := msg.Params[0]
mask, err := CanonicalizeMaskWildcard(mask)
if !strings.Contains(mask, "!") && !strings.Contains(mask, "@") { if err != nil {
mask = mask + "!*@*" rb.Add(nil, server.name, ERR_UNKNOWNERROR, details.nick, msg.Command, client.t("Erroneous nickname"))
} else if !strings.Contains(mask, "@") { return false
mask = mask + "@*"
} }
err := server.klines.RemoveMask(mask) err = server.klines.RemoveMask(mask)
if err != nil { if err != nil {
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, msg.Command, fmt.Sprintf(client.t("Could not remove ban [%s]"), err.Error())) rb.Add(nil, server.name, ERR_UNKNOWNERROR, details.nick, msg.Command, fmt.Sprintf(client.t("Could not remove ban [%s]"), err.Error()))
return false return false
} }
rb.Notice(fmt.Sprintf(client.t("Removed K-Line for %s"), mask)) rb.Notice(fmt.Sprintf(client.t("Removed K-Line for %s"), mask))
server.snomasks.Send(sno.LocalXline, fmt.Sprintf(ircfmt.Unescape("%s$r removed K-Line for %s"), client.nick, mask)) server.snomasks.Send(sno.LocalXline, fmt.Sprintf(ircfmt.Unescape("%s$r removed K-Line for %s"), details.nick, mask))
return false return false
} }

View File

@ -6,6 +6,7 @@
package irc package irc
import ( import (
"fmt"
"strings" "strings"
"github.com/oragono/confusables" "github.com/oragono/confusables"
@ -157,3 +158,55 @@ func Skeleton(name string) (string, error) {
// pass PRECIS --- we are just further canonicalizing the skeleton. // pass PRECIS --- we are just further canonicalizing the skeleton.
return cases.Lower(language.Und).String(name), nil return cases.Lower(language.Und).String(name), nil
} }
// maps a nickmask fragment to an expanded, casefolded wildcard:
// Shivaram@good-fortune -> *!shivaram@good-fortune
// EDMUND -> edmund!*@*
func CanonicalizeMaskWildcard(userhost string) (expanded string, err error) {
var nick, user, host string
bangIndex := strings.IndexByte(userhost, '!')
strudelIndex := strings.IndexByte(userhost, '@')
if bangIndex != -1 && bangIndex < strudelIndex {
nick = userhost[:bangIndex]
user = userhost[bangIndex+1 : strudelIndex]
host = userhost[strudelIndex+1:]
} else if bangIndex != -1 && strudelIndex == -1 {
nick = userhost[:bangIndex]
user = userhost[bangIndex+1:]
} else if bangIndex != -1 && strudelIndex < bangIndex {
// @ before !, fail
return "", errNicknameInvalid
} else if bangIndex == -1 && strudelIndex != -1 {
user = userhost[:strudelIndex]
host = userhost[strudelIndex+1:]
} else if bangIndex == -1 && strudelIndex == -1 {
nick = userhost
} else {
// shouldn't be possible
return "", errInvalidParams
}
if nick == "" {
nick = "*"
}
if nick != "*" {
nick, err = Casefold(nick)
if err != nil {
return "", err
}
}
if user == "" {
user = "*"
}
if user != "*" {
user = strings.ToLower(user)
}
if host == "" {
host = "*"
}
if host != "*" {
host = strings.ToLower(host)
}
return fmt.Sprintf("%s!%s@%s", nick, user, host), nil
}

View File

@ -188,3 +188,27 @@ func TestSkeleton(t *testing.T) {
// should not raise an error: // should not raise an error:
skeleton("けらんぐ") skeleton("けらんぐ")
} }
func TestCanonicalizeMaskWildcard(t *testing.T) {
tester := func(input, expected string, expectedErr error) {
out, err := CanonicalizeMaskWildcard(input)
if out != expected {
t.Errorf("expected %s to canonicalize to %s, instead %s", input, expected, out)
}
if err != expectedErr {
t.Errorf("expected %s to produce error %v, instead %v", input, expectedErr, err)
}
}
tester("shivaram", "shivaram!*@*", nil)
tester("slingamn!shivaram", "slingamn!shivaram@*", nil)
tester("ברוך", "ברוך!*@*", nil)
tester("hacker@monad.io", "*!hacker@monad.io", nil)
tester("Evan!hacker@monad.io", "evan!hacker@monad.io", nil)
tester("РОТАТО!Potato", "ротато!potato@*", nil)
tester("tkadich*", "tkadich*!*@*", nil)
tester("SLINGAMN!*@*", "slingamn!*@*", nil)
tester("slingamn!shivaram*", "slingamn!shivaram*@*", nil)
tester("slingamn!", "slingamn!*@*", nil)
tester("shivaram*@good-fortune", "*!shivaram*@good-fortune", nil)
}