mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-29 15:40:02 +01:00
Merge pull request #650 from slingamn/issue644_bans.5
two mode-related fixes
This commit is contained in:
commit
607da61bf9
@ -124,18 +124,12 @@ func (channel *Channel) applyRegInfo(chanReg RegisteredChannel) {
|
|||||||
for _, mode := range chanReg.Modes {
|
for _, mode := range chanReg.Modes {
|
||||||
channel.flags.SetMode(mode, true)
|
channel.flags.SetMode(mode, true)
|
||||||
}
|
}
|
||||||
for _, mask := range chanReg.Banlist {
|
|
||||||
channel.lists[modes.BanMask].Add(mask)
|
|
||||||
}
|
|
||||||
for _, mask := range chanReg.Exceptlist {
|
|
||||||
channel.lists[modes.ExceptMask].Add(mask)
|
|
||||||
}
|
|
||||||
for _, mask := range chanReg.Invitelist {
|
|
||||||
channel.lists[modes.InviteMask].Add(mask)
|
|
||||||
}
|
|
||||||
for account, mode := range chanReg.AccountToUMode {
|
for account, mode := range chanReg.AccountToUMode {
|
||||||
channel.accountToUMode[account] = mode
|
channel.accountToUMode[account] = mode
|
||||||
}
|
}
|
||||||
|
channel.lists[modes.BanMask].SetMasks(chanReg.Bans)
|
||||||
|
channel.lists[modes.InviteMask].SetMasks(chanReg.Invites)
|
||||||
|
channel.lists[modes.ExceptMask].SetMasks(chanReg.Excepts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// obtain a consistent snapshot of the channel state that can be persisted to the DB
|
// 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
|
|||||||
}
|
}
|
||||||
|
|
||||||
if includeFlags&IncludeLists != 0 {
|
if includeFlags&IncludeLists != 0 {
|
||||||
for mask := range channel.lists[modes.BanMask].masks {
|
info.Bans = channel.lists[modes.BanMask].Masks()
|
||||||
info.Banlist = append(info.Banlist, mask)
|
info.Invites = channel.lists[modes.InviteMask].Masks()
|
||||||
}
|
info.Excepts = channel.lists[modes.ExceptMask].Masks()
|
||||||
for mask := range channel.lists[modes.ExceptMask].masks {
|
|
||||||
info.Exceptlist = append(info.Exceptlist, mask)
|
|
||||||
}
|
|
||||||
for mask := range channel.lists[modes.InviteMask].masks {
|
|
||||||
info.Invitelist = append(info.Invitelist, mask)
|
|
||||||
}
|
|
||||||
info.AccountToUMode = make(map[string]modes.Mode)
|
info.AccountToUMode = make(map[string]modes.Mode)
|
||||||
for account, mode := range channel.accountToUMode {
|
for account, mode := range channel.accountToUMode {
|
||||||
info.AccountToUMode[account] = mode
|
info.AccountToUMode[account] = mode
|
||||||
@ -1097,14 +1085,12 @@ func (channel *Channel) ShowMaskList(client *Client, mode modes.Mode, rb *Respon
|
|||||||
}
|
}
|
||||||
|
|
||||||
nick := client.Nick()
|
nick := client.Nick()
|
||||||
channel.stateMutex.RLock()
|
chname := channel.Name()
|
||||||
// XXX don't acquire any new locks in this section, besides Socket.Write
|
for mask, info := range channel.lists[mode].Masks() {
|
||||||
for mask := range channel.lists[mode].masks {
|
rb.Add(nil, client.server.name, rpllist, nick, chname, mask, info.CreatorNickmask, strconv.FormatInt(info.TimeCreated.Unix(), 10))
|
||||||
rb.Add(nil, client.server.name, rpllist, nick, channel.name, mask)
|
|
||||||
}
|
}
|
||||||
channel.stateMutex.RUnlock()
|
|
||||||
|
|
||||||
rb.Add(nil, client.server.name, rplendoflist, nick, channel.name, client.t("End of list"))
|
rb.Add(nil, client.server.name, rplendoflist, nick, chname, client.t("End of list"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quit removes the given client from the channel
|
// Quit removes the given client from the channel
|
||||||
|
@ -88,12 +88,12 @@ type RegisteredChannel struct {
|
|||||||
Key string
|
Key string
|
||||||
// AccountToUMode maps user accounts to their persistent channel modes (e.g., +q, +h)
|
// AccountToUMode maps user accounts to their persistent channel modes (e.g., +q, +h)
|
||||||
AccountToUMode map[string]modes.Mode
|
AccountToUMode map[string]modes.Mode
|
||||||
// Banlist represents the bans set on the channel.
|
// Bans represents the bans set on the channel.
|
||||||
Banlist []string
|
Bans map[string]MaskInfo
|
||||||
// Exceptlist represents the exceptions set on the channel.
|
// Excepts represents the exceptions set on the channel.
|
||||||
Exceptlist []string
|
Excepts map[string]MaskInfo
|
||||||
// Invitelist represents the invite exceptions set on the channel.
|
// Invites represents the invite exceptions set on the channel.
|
||||||
Invitelist []string
|
Invites map[string]MaskInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChannelRegistry manages registered channels.
|
// ChannelRegistry manages registered channels.
|
||||||
@ -180,11 +180,11 @@ func (reg *ChannelRegistry) LoadChannel(nameCasefolded string) (info RegisteredC
|
|||||||
modeSlice[i] = modes.Mode(mode)
|
modeSlice[i] = modes.Mode(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
var banlist []string
|
var banlist map[string]MaskInfo
|
||||||
_ = json.Unmarshal([]byte(banlistString), &banlist)
|
_ = json.Unmarshal([]byte(banlistString), &banlist)
|
||||||
var exceptlist []string
|
var exceptlist map[string]MaskInfo
|
||||||
_ = json.Unmarshal([]byte(exceptlistString), &exceptlist)
|
_ = json.Unmarshal([]byte(exceptlistString), &exceptlist)
|
||||||
var invitelist []string
|
var invitelist map[string]MaskInfo
|
||||||
_ = json.Unmarshal([]byte(invitelistString), &invitelist)
|
_ = json.Unmarshal([]byte(invitelistString), &invitelist)
|
||||||
accountToUMode := make(map[string]modes.Mode)
|
accountToUMode := make(map[string]modes.Mode)
|
||||||
_ = json.Unmarshal([]byte(accountToUModeString), &accountToUMode)
|
_ = json.Unmarshal([]byte(accountToUModeString), &accountToUMode)
|
||||||
@ -198,9 +198,9 @@ func (reg *ChannelRegistry) LoadChannel(nameCasefolded string) (info RegisteredC
|
|||||||
TopicSetTime: time.Unix(topicSetTimeInt, 0),
|
TopicSetTime: time.Unix(topicSetTimeInt, 0),
|
||||||
Key: password,
|
Key: password,
|
||||||
Modes: modeSlice,
|
Modes: modeSlice,
|
||||||
Banlist: banlist,
|
Bans: banlist,
|
||||||
Exceptlist: exceptlist,
|
Excepts: exceptlist,
|
||||||
Invitelist: invitelist,
|
Invites: invitelist,
|
||||||
AccountToUMode: accountToUMode,
|
AccountToUMode: accountToUMode,
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -296,11 +296,11 @@ func (reg *ChannelRegistry) saveChannel(tx *buntdb.Tx, channelInfo RegisteredCha
|
|||||||
}
|
}
|
||||||
|
|
||||||
if includeFlags&IncludeLists != 0 {
|
if includeFlags&IncludeLists != 0 {
|
||||||
banlistString, _ := json.Marshal(channelInfo.Banlist)
|
banlistString, _ := json.Marshal(channelInfo.Bans)
|
||||||
tx.Set(fmt.Sprintf(keyChannelBanlist, channelKey), string(banlistString), nil)
|
tx.Set(fmt.Sprintf(keyChannelBanlist, channelKey), string(banlistString), nil)
|
||||||
exceptlistString, _ := json.Marshal(channelInfo.Exceptlist)
|
exceptlistString, _ := json.Marshal(channelInfo.Excepts)
|
||||||
tx.Set(fmt.Sprintf(keyChannelExceptlist, channelKey), string(exceptlistString), nil)
|
tx.Set(fmt.Sprintf(keyChannelExceptlist, channelKey), string(exceptlistString), nil)
|
||||||
invitelistString, _ := json.Marshal(channelInfo.Invitelist)
|
invitelistString, _ := json.Marshal(channelInfo.Invites)
|
||||||
tx.Set(fmt.Sprintf(keyChannelInvitelist, channelKey), string(invitelistString), nil)
|
tx.Set(fmt.Sprintf(keyChannelInvitelist, channelKey), string(invitelistString), nil)
|
||||||
accountToUModeString, _ := json.Marshal(channelInfo.AccountToUMode)
|
accountToUModeString, _ := json.Marshal(channelInfo.AccountToUMode)
|
||||||
tx.Set(fmt.Sprintf(keyChannelAccountToUMode, channelKey), string(accountToUModeString), nil)
|
tx.Set(fmt.Sprintf(keyChannelAccountToUMode, channelKey), string(accountToUModeString), nil)
|
||||||
|
@ -5,10 +5,9 @@
|
|||||||
package irc
|
package irc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/goshuirc/irc-go/ircmatch"
|
"github.com/goshuirc/irc-go/ircmatch"
|
||||||
|
|
||||||
@ -251,63 +250,62 @@ func (clients *ClientManager) FindAll(userhost string) (set ClientSet) {
|
|||||||
//TODO(dan): move this over to generally using glob syntax instead?
|
//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?)
|
// 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.
|
// UserMaskSet holds a set of client masks and lets you match hostnames to them.
|
||||||
type UserMaskSet struct {
|
type UserMaskSet struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
masks map[string]bool
|
masks map[string]MaskInfo
|
||||||
regexp *regexp.Regexp
|
regexp *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUserMaskSet returns a new UserMaskSet.
|
|
||||||
func NewUserMaskSet() *UserMaskSet {
|
func NewUserMaskSet() *UserMaskSet {
|
||||||
return &UserMaskSet{
|
return new(UserMaskSet)
|
||||||
masks: make(map[string]bool),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds the given mask to this set.
|
// Add adds the given mask to this set.
|
||||||
func (set *UserMaskSet) Add(mask string) (added bool) {
|
func (set *UserMaskSet) Add(mask, creatorNickmask, creatorAccount string) (maskAdded string, err error) {
|
||||||
casefoldedMask, err := Casefold(mask)
|
casefoldedMask, err := CanonicalizeMaskWildcard(mask)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(fmt.Sprintf("ERROR: Could not add mask to usermaskset: [%s]", mask))
|
return
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set.Lock()
|
set.Lock()
|
||||||
added = !set.masks[casefoldedMask]
|
if set.masks == nil {
|
||||||
if added {
|
set.masks = make(map[string]MaskInfo)
|
||||||
set.masks[casefoldedMask] = true
|
}
|
||||||
|
_, present := set.masks[casefoldedMask]
|
||||||
|
if !present {
|
||||||
|
maskAdded = casefoldedMask
|
||||||
|
set.masks[casefoldedMask] = MaskInfo{
|
||||||
|
TimeCreated: time.Now().UTC(),
|
||||||
|
CreatorNickmask: creatorNickmask,
|
||||||
|
CreatorAccount: creatorAccount,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
set.Unlock()
|
set.Unlock()
|
||||||
|
|
||||||
if added {
|
if !present {
|
||||||
set.setRegexp()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddAll adds the given masks to this set.
|
|
||||||
func (set *UserMaskSet) AddAll(masks []string) (added bool) {
|
|
||||||
set.Lock()
|
|
||||||
defer set.Unlock()
|
|
||||||
|
|
||||||
for _, mask := range masks {
|
|
||||||
if !added && !set.masks[mask] {
|
|
||||||
added = true
|
|
||||||
}
|
|
||||||
set.masks[mask] = true
|
|
||||||
}
|
|
||||||
if added {
|
|
||||||
set.setRegexp()
|
set.setRegexp()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes the given mask from this set.
|
// Remove removes the given mask from this set.
|
||||||
func (set *UserMaskSet) Remove(mask string) (removed bool) {
|
func (set *UserMaskSet) Remove(mask string) (maskRemoved string, err error) {
|
||||||
|
mask, err = CanonicalizeMaskWildcard(mask)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
set.Lock()
|
set.Lock()
|
||||||
removed = set.masks[mask]
|
_, removed := set.masks[mask]
|
||||||
if removed {
|
if removed {
|
||||||
|
maskRemoved = mask
|
||||||
delete(set.masks, mask)
|
delete(set.masks, mask)
|
||||||
}
|
}
|
||||||
set.Unlock()
|
set.Unlock()
|
||||||
@ -318,6 +316,24 @@ func (set *UserMaskSet) Remove(mask string) (removed bool) {
|
|||||||
return
|
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.
|
// Match matches the given n!u@h.
|
||||||
func (set *UserMaskSet) Match(userhost string) bool {
|
func (set *UserMaskSet) Match(userhost string) bool {
|
||||||
set.RLock()
|
set.RLock()
|
||||||
@ -330,19 +346,6 @@ func (set *UserMaskSet) Match(userhost string) bool {
|
|||||||
return regexp.MatchString(userhost)
|
return regexp.MatchString(userhost)
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the masks in this set.
|
|
||||||
func (set *UserMaskSet) String() string {
|
|
||||||
set.RLock()
|
|
||||||
masks := make([]string, len(set.masks))
|
|
||||||
index := 0
|
|
||||||
for mask := range set.masks {
|
|
||||||
masks[index] = mask
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
set.RUnlock()
|
|
||||||
return strings.Join(masks, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (set *UserMaskSet) Length() int {
|
func (set *UserMaskSet) Length() int {
|
||||||
set.RLock()
|
set.RLock()
|
||||||
defer set.RUnlock()
|
defer set.RUnlock()
|
||||||
|
@ -22,7 +22,7 @@ const (
|
|||||||
// 'version' of the database schema
|
// 'version' of the database schema
|
||||||
keySchemaVersion = "db.version"
|
keySchemaVersion = "db.version"
|
||||||
// latest schema of the db
|
// latest schema of the db
|
||||||
latestDbSchema = "6"
|
latestDbSchema = "7"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SchemaChanger func(*Config, *buntdb.Tx) error
|
type SchemaChanger func(*Config, *buntdb.Tx) error
|
||||||
@ -440,6 +440,66 @@ func schemaChangeV5ToV6(config *Config, tx *buntdb.Tx) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type maskInfoV7 struct {
|
||||||
|
TimeCreated time.Time
|
||||||
|
CreatorNickmask string
|
||||||
|
CreatorAccount string
|
||||||
|
}
|
||||||
|
|
||||||
|
func schemaChangeV6ToV7(config *Config, tx *buntdb.Tx) error {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
var channels []string
|
||||||
|
prefix := "channel.exists "
|
||||||
|
tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
|
||||||
|
if !strings.HasPrefix(key, prefix) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
channels = append(channels, strings.TrimPrefix(key, prefix))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
converter := func(key string) {
|
||||||
|
oldRawValue, err := tx.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var masks []string
|
||||||
|
err = json.Unmarshal([]byte(oldRawValue), &masks)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newCookedValue := make(map[string]maskInfoV7)
|
||||||
|
for _, mask := range masks {
|
||||||
|
normalizedMask, err := CanonicalizeMaskWildcard(mask)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newCookedValue[normalizedMask] = maskInfoV7{
|
||||||
|
TimeCreated: now,
|
||||||
|
CreatorNickmask: "*",
|
||||||
|
CreatorAccount: "*",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newRawValue, err := json.Marshal(newCookedValue)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tx.Set(key, string(newRawValue), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixes := []string{
|
||||||
|
"channel.banlist %s",
|
||||||
|
"channel.exceptlist %s",
|
||||||
|
"channel.invitelist %s",
|
||||||
|
}
|
||||||
|
for _, channel := range channels {
|
||||||
|
for _, prefix := range prefixes {
|
||||||
|
converter(fmt.Sprintf(prefix, channel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
allChanges := []SchemaChange{
|
allChanges := []SchemaChange{
|
||||||
{
|
{
|
||||||
@ -467,6 +527,11 @@ func init() {
|
|||||||
TargetVersion: "6",
|
TargetVersion: "6",
|
||||||
Changer: schemaChangeV5ToV6,
|
Changer: schemaChangeV5ToV6,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
InitialVersion: "6",
|
||||||
|
TargetVersion: "7",
|
||||||
|
Changer: schemaChangeV6ToV7,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// build the index
|
// build the index
|
||||||
|
@ -1685,13 +1685,12 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// applied mode changes
|
var changes modes.ModeChanges
|
||||||
applied := make(modes.ModeChanges, 0)
|
|
||||||
|
|
||||||
if 1 < len(msg.Params) {
|
if 1 < len(msg.Params) {
|
||||||
// parse out real mode changes
|
// parse out real mode changes
|
||||||
params := msg.Params[1:]
|
params := msg.Params[1:]
|
||||||
changes, unknown := modes.ParseChannelModeChanges(params...)
|
var unknown map[rune]bool
|
||||||
|
changes, unknown = modes.ParseChannelModeChanges(params...)
|
||||||
|
|
||||||
// alert for unknown mode changes
|
// alert for unknown mode changes
|
||||||
for char := range unknown {
|
for char := range unknown {
|
||||||
@ -1700,10 +1699,9 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
|
|||||||
if len(unknown) == 1 && len(changes) == 0 {
|
if len(unknown) == 1 && len(changes) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply mode changes
|
|
||||||
applied = channel.ApplyChannelModeChanges(client, msg.Command == "SAMODE", changes, rb)
|
|
||||||
}
|
}
|
||||||
|
// process mode changes, include list operations (an empty set of changes does a list)
|
||||||
|
applied := channel.ApplyChannelModeChanges(client, msg.Command == "SAMODE", changes, rb)
|
||||||
|
|
||||||
// save changes
|
// save changes
|
||||||
var includeFlags uint
|
var includeFlags uint
|
||||||
@ -1719,8 +1717,8 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
|
|||||||
}
|
}
|
||||||
|
|
||||||
// send out changes
|
// send out changes
|
||||||
prefix := client.NickMaskString()
|
|
||||||
if len(applied) > 0 {
|
if len(applied) > 0 {
|
||||||
|
prefix := client.NickMaskString()
|
||||||
//TODO(dan): we should change the name of String and make it return a slice here
|
//TODO(dan): we should change the name of String and make it return a slice here
|
||||||
args := append([]string{channel.name}, strings.Split(applied.String(), " ")...)
|
args := append([]string{channel.name}, strings.Split(applied.String(), " ")...)
|
||||||
for _, member := range channel.Members() {
|
for _, member := range channel.Members() {
|
||||||
@ -1735,10 +1733,6 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
|
|||||||
member.Send(nil, prefix, "MODE", args...)
|
member.Send(nil, prefix, "MODE", args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
args := append([]string{client.nick, channel.name}, channel.modeStrings(client)...)
|
|
||||||
rb.Add(nil, prefix, RPL_CHANNELMODEIS, args...)
|
|
||||||
rb.Add(nil, client.nickMaskString, RPL_CHANNELCREATED, client.nick, channel.name, strconv.FormatInt(channel.createdTime.Unix(), 10))
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
69
irc/modes.go
69
irc/modes.go
@ -6,6 +6,7 @@
|
|||||||
package irc
|
package irc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -104,17 +105,15 @@ func ParseDefaultChannelModes(rawModes *string) modes.Modes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ApplyChannelModeChanges applies a given set of mode changes.
|
// ApplyChannelModeChanges applies a given set of mode changes.
|
||||||
func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, changes modes.ModeChanges, rb *ResponseBuffer) modes.ModeChanges {
|
func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, changes modes.ModeChanges, rb *ResponseBuffer) (applied modes.ModeChanges) {
|
||||||
// so we only output one warning for each list type when full
|
// so we only output one warning for each list type when full
|
||||||
listFullWarned := make(map[modes.Mode]bool)
|
listFullWarned := make(map[modes.Mode]bool)
|
||||||
|
|
||||||
var alreadySentPrivError bool
|
var alreadySentPrivError bool
|
||||||
|
|
||||||
applied := make(modes.ModeChanges, 0)
|
maskOpCount := 0
|
||||||
|
chname := channel.Name()
|
||||||
isListOp := func(change modes.ModeChange) bool {
|
details := client.Details()
|
||||||
return (change.Op == modes.List) || (change.Arg == "")
|
|
||||||
}
|
|
||||||
|
|
||||||
hasPrivs := func(change modes.ModeChange) bool {
|
hasPrivs := func(change modes.ModeChange) bool {
|
||||||
if isSamode {
|
if isSamode {
|
||||||
@ -127,18 +126,19 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
cfarg, _ := CasefoldName(change.Arg)
|
cfarg, _ := CasefoldName(change.Arg)
|
||||||
isSelfChange := cfarg == client.NickCasefolded()
|
isSelfChange := cfarg == details.nickCasefolded
|
||||||
if change.Op == modes.Remove && isSelfChange {
|
if change.Op == modes.Remove && isSelfChange {
|
||||||
// "There is no restriction, however, on anyone `deopping' themselves"
|
// "There is no restriction, however, on anyone `deopping' themselves"
|
||||||
// <https://tools.ietf.org/html/rfc2812#section-3.1.5>
|
// <https://tools.ietf.org/html/rfc2812#section-3.1.5>
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return channelUserModeHasPrivsOver(channel.HighestUserMode(client), change.Mode)
|
return channelUserModeHasPrivsOver(channel.HighestUserMode(client), change.Mode)
|
||||||
case modes.BanMask:
|
case modes.InviteMask, modes.ExceptMask:
|
||||||
// #163: allow unprivileged users to list ban masks
|
// listing these requires privileges
|
||||||
return isListOp(change) || channel.ClientIsAtLeast(client, modes.ChannelOperator)
|
|
||||||
default:
|
|
||||||
return channel.ClientIsAtLeast(client, modes.ChannelOperator)
|
return channel.ClientIsAtLeast(client, modes.ChannelOperator)
|
||||||
|
default:
|
||||||
|
// #163: allow unprivileged users to list ban masks, and any other modes
|
||||||
|
return change.Op == modes.List || channel.ClientIsAtLeast(client, modes.ChannelOperator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,40 +146,52 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c
|
|||||||
if !hasPrivs(change) {
|
if !hasPrivs(change) {
|
||||||
if !alreadySentPrivError {
|
if !alreadySentPrivError {
|
||||||
alreadySentPrivError = true
|
alreadySentPrivError = true
|
||||||
rb.Add(nil, client.server.name, ERR_CHANOPRIVSNEEDED, client.Nick(), channel.name, client.t("You're not a channel operator"))
|
rb.Add(nil, client.server.name, ERR_CHANOPRIVSNEEDED, details.nick, channel.name, client.t("You're not a channel operator"))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch change.Mode {
|
switch change.Mode {
|
||||||
case modes.BanMask, modes.ExceptMask, modes.InviteMask:
|
case modes.BanMask, modes.ExceptMask, modes.InviteMask:
|
||||||
if isListOp(change) {
|
maskOpCount += 1
|
||||||
|
if change.Op == modes.List {
|
||||||
channel.ShowMaskList(client, change.Mode, rb)
|
channel.ShowMaskList(client, change.Mode, rb)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// confirm mask looks valid
|
mask := change.Arg
|
||||||
mask, err := Casefold(change.Arg)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch change.Op {
|
switch change.Op {
|
||||||
case modes.Add:
|
case modes.Add:
|
||||||
if channel.lists[change.Mode].Length() >= client.server.Config().Limits.ChanListModes {
|
if channel.lists[change.Mode].Length() >= client.server.Config().Limits.ChanListModes {
|
||||||
if !listFullWarned[change.Mode] {
|
if !listFullWarned[change.Mode] {
|
||||||
rb.Add(nil, client.server.name, ERR_BANLISTFULL, client.Nick(), channel.Name(), change.Mode.String(), client.t("Channel list is full"))
|
rb.Add(nil, client.server.name, ERR_BANLISTFULL, details.nick, chname, change.Mode.String(), client.t("Channel list is full"))
|
||||||
listFullWarned[change.Mode] = true
|
listFullWarned[change.Mode] = true
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.lists[change.Mode].Add(mask)
|
maskAdded, err := channel.lists[change.Mode].Add(mask, details.nickMask, details.accountName)
|
||||||
applied = append(applied, change)
|
if maskAdded != "" {
|
||||||
|
appliedChange := change
|
||||||
|
appliedChange.Arg = maskAdded
|
||||||
|
applied = append(applied, appliedChange)
|
||||||
|
} else if err != nil {
|
||||||
|
rb.Add(nil, client.server.name, ERR_INVALIDMODEPARAM, details.nick, mask, fmt.Sprintf(client.t("Invalid mode %s parameter: %s"), string(change.Mode), mask))
|
||||||
|
} else {
|
||||||
|
rb.Add(nil, client.server.name, ERR_LISTMODEALREADYSET, chname, mask, string(change.Mode), fmt.Sprintf(client.t("Channel %s list already contains %s"), chname, mask))
|
||||||
|
}
|
||||||
|
|
||||||
case modes.Remove:
|
case modes.Remove:
|
||||||
channel.lists[change.Mode].Remove(mask)
|
maskRemoved, err := channel.lists[change.Mode].Remove(mask)
|
||||||
applied = append(applied, change)
|
if maskRemoved != "" {
|
||||||
|
appliedChange := change
|
||||||
|
appliedChange.Arg = maskRemoved
|
||||||
|
applied = append(applied, appliedChange)
|
||||||
|
} else if err != nil {
|
||||||
|
rb.Add(nil, client.server.name, ERR_INVALIDMODEPARAM, details.nick, mask, fmt.Sprintf(client.t("Invalid mode %s parameter: %s"), string(change.Mode), mask))
|
||||||
|
} else {
|
||||||
|
rb.Add(nil, client.server.name, ERR_LISTMODENOTSET, chname, mask, string(change.Mode), fmt.Sprintf(client.t("Channel %s list does not contain %s"), chname, mask))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case modes.UserLimit:
|
case modes.UserLimit:
|
||||||
@ -223,7 +235,7 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c
|
|||||||
nick := change.Arg
|
nick := change.Arg
|
||||||
if nick == "" {
|
if nick == "" {
|
||||||
rb.Add(nil, client.server.name, ERR_NEEDMOREPARAMS, client.Nick(), "MODE", client.t("Not enough parameters"))
|
rb.Add(nil, client.server.name, ERR_NEEDMOREPARAMS, client.Nick(), "MODE", client.t("Not enough parameters"))
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
change := channel.applyModeToMember(client, change.Mode, change.Op, nick, rb)
|
change := channel.applyModeToMember(client, change.Mode, change.Op, nick, rb)
|
||||||
@ -233,6 +245,13 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #649: don't send 324 RPL_CHANNELMODEIS if we were only working with mask lists
|
||||||
|
if len(applied) == 0 && !alreadySentPrivError && (maskOpCount == 0 || maskOpCount < len(changes)) {
|
||||||
|
args := append([]string{details.nick, chname}, channel.modeStrings(client)...)
|
||||||
|
rb.Add(nil, client.server.name, RPL_CHANNELMODEIS, args...)
|
||||||
|
rb.Add(nil, client.server.name, RPL_CREATIONTIME, details.nick, chname, strconv.FormatInt(channel.createdTime.Unix(), 10))
|
||||||
|
}
|
||||||
|
|
||||||
return applied
|
return applied
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +47,19 @@ func TestParseChannelModeChanges(t *testing.T) {
|
|||||||
if len(modes) != 1 || modes[0] != expected {
|
if len(modes) != 1 || modes[0] != expected {
|
||||||
t.Errorf("unexpected mode change: %v", modes)
|
t.Errorf("unexpected mode change: %v", modes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modes, unknown = ParseChannelModeChanges("+b")
|
||||||
|
if len(unknown) > 0 {
|
||||||
|
t.Errorf("unexpected unknown mode change: %v", unknown)
|
||||||
|
}
|
||||||
|
// +b with no argument becomes a list operation
|
||||||
|
expectedChanges := ModeChanges{{
|
||||||
|
Op: List,
|
||||||
|
Mode: BanMask,
|
||||||
|
}}
|
||||||
|
if !reflect.DeepEqual(modes, expectedChanges) {
|
||||||
|
t.Errorf("unexpected mode change: %v instead of %v", modes, expectedChanges)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetMode(t *testing.T) {
|
func TestSetMode(t *testing.T) {
|
||||||
|
@ -70,7 +70,7 @@ const (
|
|||||||
RPL_LISTEND = "323"
|
RPL_LISTEND = "323"
|
||||||
RPL_CHANNELMODEIS = "324"
|
RPL_CHANNELMODEIS = "324"
|
||||||
RPL_UNIQOPIS = "325"
|
RPL_UNIQOPIS = "325"
|
||||||
RPL_CHANNELCREATED = "329"
|
RPL_CREATIONTIME = "329"
|
||||||
RPL_WHOISACCOUNT = "330"
|
RPL_WHOISACCOUNT = "330"
|
||||||
RPL_NOTOPIC = "331"
|
RPL_NOTOPIC = "331"
|
||||||
RPL_TOPIC = "332"
|
RPL_TOPIC = "332"
|
||||||
@ -169,6 +169,9 @@ const (
|
|||||||
RPL_YOURLANGUAGESARE = "687"
|
RPL_YOURLANGUAGESARE = "687"
|
||||||
ERR_CHANNAMEINUSE = "692"
|
ERR_CHANNAMEINUSE = "692"
|
||||||
ERR_CANNOTRENAME = "693"
|
ERR_CANNOTRENAME = "693"
|
||||||
|
ERR_INVALIDMODEPARAM = "696"
|
||||||
|
ERR_LISTMODEALREADYSET = "697"
|
||||||
|
ERR_LISTMODENOTSET = "698"
|
||||||
RPL_HELPSTART = "704"
|
RPL_HELPSTART = "704"
|
||||||
RPL_HELPTXT = "705"
|
RPL_HELPTXT = "705"
|
||||||
RPL_ENDOFHELP = "706"
|
RPL_ENDOFHELP = "706"
|
||||||
|
@ -8,6 +8,7 @@ package irc
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/oragono/confusables"
|
"github.com/oragono/confusables"
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
@ -191,9 +192,15 @@ func CanonicalizeMaskWildcard(userhost string) (expanded string, err error) {
|
|||||||
nick = "*"
|
nick = "*"
|
||||||
}
|
}
|
||||||
if nick != "*" {
|
if nick != "*" {
|
||||||
nick, err = Casefold(nick)
|
// XXX allow nick wildcards in pure ASCII, but not in unicode,
|
||||||
if err != nil {
|
// because the * character breaks casefolding
|
||||||
return "", err
|
if IsPureASCII(nick) {
|
||||||
|
nick = strings.ToLower(nick)
|
||||||
|
} else {
|
||||||
|
nick, err = Casefold(nick)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if user == "" {
|
if user == "" {
|
||||||
@ -210,3 +217,12 @@ func CanonicalizeMaskWildcard(userhost string) (expanded string, err error) {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf("%s!%s@%s", nick, user, host), nil
|
return fmt.Sprintf("%s!%s@%s", nick, user, host), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsPureASCII(str string) bool {
|
||||||
|
for i := 0; i < len(str); i++ {
|
||||||
|
if unicode.MaxASCII < str[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
@ -211,4 +211,5 @@ func TestCanonicalizeMaskWildcard(t *testing.T) {
|
|||||||
tester("slingamn!shivaram*", "slingamn!shivaram*@*", nil)
|
tester("slingamn!shivaram*", "slingamn!shivaram*@*", nil)
|
||||||
tester("slingamn!", "slingamn!*@*", nil)
|
tester("slingamn!", "slingamn!*@*", nil)
|
||||||
tester("shivaram*@good-fortune", "*!shivaram*@good-fortune", nil)
|
tester("shivaram*@good-fortune", "*!shivaram*@good-fortune", nil)
|
||||||
|
tester("shivaram*", "shivaram*!*@*", nil)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user