mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-10 22:19:31 +01:00
fix #581
This commit is contained in:
parent
07865b8f63
commit
c5a81d59ff
@ -53,13 +53,7 @@ type Channel struct {
|
|||||||
|
|
||||||
// NewChannel creates a new channel from a `Server` and a `name`
|
// NewChannel creates a new channel from a `Server` and a `name`
|
||||||
// string, which must be unique on the server.
|
// string, which must be unique on the server.
|
||||||
func NewChannel(s *Server, name string, registered bool) *Channel {
|
func NewChannel(s *Server, name, casefoldedName string, registered bool) *Channel {
|
||||||
casefoldedName, err := CasefoldChannel(name)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("internal", "Bad channel name", name, err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
config := s.Config()
|
config := s.Config()
|
||||||
|
|
||||||
channel := &Channel{
|
channel := &Channel{
|
||||||
|
@ -13,6 +13,7 @@ type channelManagerEntry struct {
|
|||||||
// think the channel is empty (without holding a lock across the entire Channel.Join()
|
// think the channel is empty (without holding a lock across the entire Channel.Join()
|
||||||
// call)
|
// call)
|
||||||
pendingJoins int
|
pendingJoins int
|
||||||
|
skeleton string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChannelManager keeps track of all the channels on the server,
|
// ChannelManager keeps track of all the channels on the server,
|
||||||
@ -20,29 +21,46 @@ type channelManagerEntry struct {
|
|||||||
// cleanup of empty channels on last part, and renames.
|
// cleanup of empty channels on last part, and renames.
|
||||||
type ChannelManager struct {
|
type ChannelManager struct {
|
||||||
sync.RWMutex // tier 2
|
sync.RWMutex // tier 2
|
||||||
|
// chans is the main data structure, mapping casefolded name -> *Channel
|
||||||
chans map[string]*channelManagerEntry
|
chans map[string]*channelManagerEntry
|
||||||
registeredChannels map[string]bool
|
chansSkeletons StringSet // skeletons of *unregistered* chans
|
||||||
purgedChannels map[string]empty
|
registeredChannels StringSet // casefolds of registered chans
|
||||||
|
registeredSkeletons StringSet // skeletons of registered chans
|
||||||
|
purgedChannels StringSet // casefolds of purged chans
|
||||||
server *Server
|
server *Server
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewChannelManager returns a new ChannelManager.
|
// NewChannelManager returns a new ChannelManager.
|
||||||
func (cm *ChannelManager) Initialize(server *Server) {
|
func (cm *ChannelManager) Initialize(server *Server) {
|
||||||
cm.chans = make(map[string]*channelManagerEntry)
|
cm.chans = make(map[string]*channelManagerEntry)
|
||||||
|
cm.chansSkeletons = make(StringSet)
|
||||||
cm.server = server
|
cm.server = server
|
||||||
|
|
||||||
if server.Config().Channels.Registration.Enabled {
|
if server.Config().Channels.Registration.Enabled {
|
||||||
cm.loadRegisteredChannels()
|
cm.loadRegisteredChannels()
|
||||||
}
|
}
|
||||||
|
// purging should work even if registration is disabled
|
||||||
|
cm.purgedChannels = cm.server.channelRegistry.PurgedChannels()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *ChannelManager) loadRegisteredChannels() {
|
func (cm *ChannelManager) loadRegisteredChannels() {
|
||||||
registeredChannels := cm.server.channelRegistry.AllChannels()
|
rawNames := cm.server.channelRegistry.AllChannels()
|
||||||
purgedChannels := cm.server.channelRegistry.PurgedChannels()
|
registeredChannels := make(StringSet, len(rawNames))
|
||||||
|
registeredSkeletons := make(StringSet, len(rawNames))
|
||||||
|
for _, name := range rawNames {
|
||||||
|
cfname, err := CasefoldChannel(name)
|
||||||
|
if err == nil {
|
||||||
|
registeredChannels.Add(cfname)
|
||||||
|
}
|
||||||
|
skeleton, err := Skeleton(name)
|
||||||
|
if err == nil {
|
||||||
|
registeredSkeletons.Add(skeleton)
|
||||||
|
}
|
||||||
|
}
|
||||||
cm.Lock()
|
cm.Lock()
|
||||||
defer cm.Unlock()
|
defer cm.Unlock()
|
||||||
cm.registeredChannels = registeredChannels
|
cm.registeredChannels = registeredChannels
|
||||||
cm.purgedChannels = purgedChannels
|
cm.registeredSkeletons = registeredSkeletons
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns an existing channel with name equivalent to `name`, or nil
|
// Get returns an existing channel with name equivalent to `name`, or nil
|
||||||
@ -64,37 +82,49 @@ func (cm *ChannelManager) Get(name string) (channel *Channel) {
|
|||||||
func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin bool, rb *ResponseBuffer) error {
|
func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin bool, rb *ResponseBuffer) error {
|
||||||
server := client.server
|
server := client.server
|
||||||
casefoldedName, err := CasefoldChannel(name)
|
casefoldedName, err := CasefoldChannel(name)
|
||||||
if err != nil || len(casefoldedName) > server.Config().Limits.ChannelLen {
|
skeleton, skerr := Skeleton(name)
|
||||||
|
if err != nil || skerr != nil || len(casefoldedName) > server.Config().Limits.ChannelLen {
|
||||||
return errNoSuchChannel
|
return errNoSuchChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
channel := func() *Channel {
|
channel, err := func() (*Channel, error) {
|
||||||
cm.Lock()
|
cm.Lock()
|
||||||
defer cm.Unlock()
|
defer cm.Unlock()
|
||||||
|
|
||||||
_, purged := cm.purgedChannels[casefoldedName]
|
if cm.purgedChannels.Has(casefoldedName) {
|
||||||
if purged {
|
return nil, errChannelPurged
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
entry := cm.chans[casefoldedName]
|
entry := cm.chans[casefoldedName]
|
||||||
if entry == nil {
|
if entry == nil {
|
||||||
registered := cm.registeredChannels[casefoldedName]
|
registered := cm.registeredChannels.Has(casefoldedName)
|
||||||
// enforce OpOnlyCreation
|
// enforce OpOnlyCreation
|
||||||
if !registered && server.Config().Channels.OpOnlyCreation && !client.HasRoleCapabs("chanreg") {
|
if !registered && server.Config().Channels.OpOnlyCreation && !client.HasRoleCapabs("chanreg") {
|
||||||
return nil
|
return nil, errInsufficientPrivs
|
||||||
|
}
|
||||||
|
// enforce confusables
|
||||||
|
if cm.chansSkeletons.Has(skeleton) || (!registered && cm.registeredSkeletons.Has(skeleton)) {
|
||||||
|
return nil, errConfusableIdentifier
|
||||||
}
|
}
|
||||||
entry = &channelManagerEntry{
|
entry = &channelManagerEntry{
|
||||||
channel: NewChannel(server, name, registered),
|
channel: NewChannel(server, name, casefoldedName, registered),
|
||||||
pendingJoins: 0,
|
pendingJoins: 0,
|
||||||
}
|
}
|
||||||
|
if !registered {
|
||||||
|
// for an unregistered channel, we already have the correct unfolded name
|
||||||
|
// and therefore the final skeleton. for a registered channel, we don't have
|
||||||
|
// the unfolded name yet (it needs to be loaded from the db), but we already
|
||||||
|
// have the final skeleton in `registeredSkeletons` so we don't need to track it
|
||||||
|
cm.chansSkeletons.Add(skeleton)
|
||||||
|
entry.skeleton = skeleton
|
||||||
|
}
|
||||||
cm.chans[casefoldedName] = entry
|
cm.chans[casefoldedName] = entry
|
||||||
}
|
}
|
||||||
entry.pendingJoins += 1
|
entry.pendingJoins += 1
|
||||||
return entry.channel
|
return entry.channel, nil
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if channel == nil {
|
if err != nil {
|
||||||
return errNoSuchChannel
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.EnsureLoaded()
|
channel.EnsureLoaded()
|
||||||
@ -109,8 +139,9 @@ func (cm *ChannelManager) maybeCleanup(channel *Channel, afterJoin bool) {
|
|||||||
cm.Lock()
|
cm.Lock()
|
||||||
defer cm.Unlock()
|
defer cm.Unlock()
|
||||||
|
|
||||||
nameCasefolded := channel.NameCasefolded()
|
cfname := channel.NameCasefolded()
|
||||||
entry := cm.chans[nameCasefolded]
|
|
||||||
|
entry := cm.chans[cfname]
|
||||||
if entry == nil || entry.channel != channel {
|
if entry == nil || entry.channel != channel {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -119,7 +150,10 @@ func (cm *ChannelManager) maybeCleanup(channel *Channel, afterJoin bool) {
|
|||||||
entry.pendingJoins -= 1
|
entry.pendingJoins -= 1
|
||||||
}
|
}
|
||||||
if entry.pendingJoins == 0 && entry.channel.IsClean() {
|
if entry.pendingJoins == 0 && entry.channel.IsClean() {
|
||||||
delete(cm.chans, nameCasefolded)
|
delete(cm.chans, cfname)
|
||||||
|
if entry.skeleton != "" {
|
||||||
|
delete(cm.chansSkeletons, entry.skeleton)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +211,7 @@ func (cm *ChannelManager) SetRegistered(channelName string, account string) (err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cm.registeredChannels[cfname] = true
|
cm.registeredChannels.Add(cfname)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,13 +245,17 @@ func (cm *ChannelManager) SetUnregistered(channelName string, account string) (e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rename renames a channel (but does not notify the members)
|
// Rename renames a channel (but does not notify the members)
|
||||||
func (cm *ChannelManager) Rename(name string, newname string) (err error) {
|
func (cm *ChannelManager) Rename(name string, newName string) (err error) {
|
||||||
cfname, err := CasefoldChannel(name)
|
cfname, err := CasefoldChannel(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errNoSuchChannel
|
return errNoSuchChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
cfnewname, err := CasefoldChannel(newname)
|
newCfname, err := CasefoldChannel(newName)
|
||||||
|
if err != nil {
|
||||||
|
return errInvalidChannelName
|
||||||
|
}
|
||||||
|
newSkeleton, err := Skeleton(newName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errInvalidChannelName
|
return errInvalidChannelName
|
||||||
}
|
}
|
||||||
@ -236,22 +274,35 @@ func (cm *ChannelManager) Rename(name string, newname string) (err error) {
|
|||||||
cm.Lock()
|
cm.Lock()
|
||||||
defer cm.Unlock()
|
defer cm.Unlock()
|
||||||
|
|
||||||
if cm.chans[cfnewname] != nil || cm.registeredChannels[cfnewname] {
|
if cm.chans[newCfname] != nil || cm.registeredChannels.Has(newCfname) {
|
||||||
|
return errChannelNameInUse
|
||||||
|
}
|
||||||
|
if cm.chansSkeletons.Has(newSkeleton) || cm.registeredSkeletons.Has(newSkeleton) {
|
||||||
return errChannelNameInUse
|
return errChannelNameInUse
|
||||||
}
|
}
|
||||||
entry := cm.chans[cfname]
|
entry := cm.chans[cfname]
|
||||||
if entry == nil {
|
if entry == nil || !entry.channel.IsLoaded() {
|
||||||
return errNoSuchChannel
|
return errNoSuchChannel
|
||||||
}
|
}
|
||||||
channel = entry.channel
|
channel = entry.channel
|
||||||
info = channel.ExportRegistration(IncludeInitial)
|
info = channel.ExportRegistration(IncludeInitial)
|
||||||
|
registered := info.Founder != ""
|
||||||
delete(cm.chans, cfname)
|
delete(cm.chans, cfname)
|
||||||
cm.chans[cfnewname] = entry
|
cm.chans[newCfname] = entry
|
||||||
if cm.registeredChannels[cfname] {
|
if registered {
|
||||||
delete(cm.registeredChannels, cfname)
|
delete(cm.registeredChannels, cfname)
|
||||||
cm.registeredChannels[cfnewname] = true
|
if oldSkeleton, err := Skeleton(info.Name); err == nil {
|
||||||
|
delete(cm.registeredSkeletons, oldSkeleton)
|
||||||
}
|
}
|
||||||
entry.channel.Rename(newname, cfnewname)
|
cm.registeredChannels.Add(newCfname)
|
||||||
|
cm.registeredSkeletons.Add(newSkeleton)
|
||||||
|
} else {
|
||||||
|
delete(cm.chansSkeletons, entry.skeleton)
|
||||||
|
cm.chansSkeletons.Add(newSkeleton)
|
||||||
|
entry.skeleton = newSkeleton
|
||||||
|
cm.chans[cfname] = entry
|
||||||
|
}
|
||||||
|
entry.channel.Rename(newName, newCfname)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,7 +334,7 @@ func (cm *ChannelManager) Purge(chname string, record ChannelPurgeRecord) (err e
|
|||||||
}
|
}
|
||||||
|
|
||||||
cm.Lock()
|
cm.Lock()
|
||||||
cm.purgedChannels[chname] = empty{}
|
cm.purgedChannels.Add(chname)
|
||||||
cm.Unlock()
|
cm.Unlock()
|
||||||
|
|
||||||
cm.server.channelRegistry.PurgeChannel(chname, record)
|
cm.server.channelRegistry.PurgeChannel(chname, record)
|
||||||
@ -298,7 +349,7 @@ func (cm *ChannelManager) IsPurged(chname string) (result bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cm.Lock()
|
cm.Lock()
|
||||||
_, result = cm.purgedChannels[chname]
|
result = cm.purgedChannels.Has(chname)
|
||||||
cm.Unlock()
|
cm.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -311,7 +362,7 @@ func (cm *ChannelManager) Unpurge(chname string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cm.Lock()
|
cm.Lock()
|
||||||
_, found := cm.purgedChannels[chname]
|
found := cm.purgedChannels.Has(chname)
|
||||||
delete(cm.purgedChannels, chname)
|
delete(cm.purgedChannels, chname)
|
||||||
cm.Unlock()
|
cm.Unlock()
|
||||||
|
|
||||||
|
@ -114,17 +114,15 @@ func (reg *ChannelRegistry) Initialize(server *Server) {
|
|||||||
reg.server = server
|
reg.server = server
|
||||||
}
|
}
|
||||||
|
|
||||||
func (reg *ChannelRegistry) AllChannels() (result map[string]bool) {
|
// AllChannels returns the uncasefolded names of all registered channels.
|
||||||
result = make(map[string]bool)
|
func (reg *ChannelRegistry) AllChannels() (result []string) {
|
||||||
|
prefix := fmt.Sprintf(keyChannelName, "")
|
||||||
prefix := fmt.Sprintf(keyChannelExists, "")
|
|
||||||
reg.server.store.View(func(tx *buntdb.Tx) error {
|
reg.server.store.View(func(tx *buntdb.Tx) error {
|
||||||
return tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
|
return tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
|
||||||
if !strings.HasPrefix(key, prefix) {
|
if !strings.HasPrefix(key, prefix) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
channel := strings.TrimPrefix(key, prefix)
|
result = append(result, value)
|
||||||
result[channel] = true
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -132,7 +130,7 @@ func (reg *ChannelRegistry) AllChannels() (result map[string]bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// PurgedChannels returns the set of all channel names that have been purged
|
// PurgedChannels returns the set of all casefolded channel names that have been purged
|
||||||
func (reg *ChannelRegistry) PurgedChannels() (result map[string]empty) {
|
func (reg *ChannelRegistry) PurgedChannels() (result map[string]empty) {
|
||||||
result = make(map[string]empty)
|
result = make(map[string]empty)
|
||||||
|
|
||||||
|
@ -41,6 +41,8 @@ var (
|
|||||||
errNicknameReserved = errors.New("nickname is reserved")
|
errNicknameReserved = errors.New("nickname is reserved")
|
||||||
errNoExistingBan = errors.New("Ban does not exist")
|
errNoExistingBan = errors.New("Ban does not exist")
|
||||||
errNoSuchChannel = errors.New(`No such channel`)
|
errNoSuchChannel = errors.New(`No such channel`)
|
||||||
|
errChannelPurged = errors.New(`This channel was purged by the server operators and cannot be used`)
|
||||||
|
errConfusableIdentifier = errors.New("This identifier is confusable with one already in use")
|
||||||
errInsufficientPrivs = errors.New("Insufficient privileges")
|
errInsufficientPrivs = errors.New("Insufficient privileges")
|
||||||
errInvalidUsername = errors.New("Invalid username")
|
errInvalidUsername = errors.New("Invalid username")
|
||||||
errFeatureDisabled = errors.New(`That feature is disabled`)
|
errFeatureDisabled = errors.New(`That feature is disabled`)
|
||||||
|
@ -1290,13 +1290,28 @@ func joinHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
|
|||||||
key = keys[i]
|
key = keys[i]
|
||||||
}
|
}
|
||||||
err := server.channels.Join(client, name, key, false, rb)
|
err := server.channels.Join(client, name, key, false, rb)
|
||||||
if err == errNoSuchChannel {
|
if err != nil {
|
||||||
rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.Nick(), utils.SafeErrorParam(name), client.t("No such channel"))
|
sendJoinError(client, name, rb, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sendJoinError(client *Client, name string, rb *ResponseBuffer, err error) {
|
||||||
|
var errMsg string
|
||||||
|
switch err {
|
||||||
|
case errInsufficientPrivs:
|
||||||
|
errMsg = `Only server operators can create new channels`
|
||||||
|
case errConfusableIdentifier:
|
||||||
|
errMsg = `That channel name is too close to the name of another channel`
|
||||||
|
case errChannelPurged:
|
||||||
|
errMsg = err.Error()
|
||||||
|
default:
|
||||||
|
errMsg = `No such channel`
|
||||||
|
}
|
||||||
|
rb.Add(nil, client.server.name, ERR_NOSUCHCHANNEL, client.Nick(), utils.SafeErrorParam(name), client.t(errMsg))
|
||||||
|
}
|
||||||
|
|
||||||
// SAJOIN [nick] #channel{,#channel}
|
// SAJOIN [nick] #channel{,#channel}
|
||||||
func sajoinHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
func sajoinHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
var target *Client
|
var target *Client
|
||||||
@ -1306,7 +1321,7 @@ func sajoinHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
|||||||
channelString = msg.Params[0]
|
channelString = msg.Params[0]
|
||||||
} else {
|
} else {
|
||||||
if len(msg.Params) == 1 {
|
if len(msg.Params) == 1 {
|
||||||
rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), "KICK", client.t("Not enough parameters"))
|
rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), "SAJOIN", client.t("Not enough parameters"))
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
target = server.clients.Get(msg.Params[0])
|
target = server.clients.Get(msg.Params[0])
|
||||||
@ -1320,7 +1335,10 @@ func sajoinHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
|||||||
|
|
||||||
channels := strings.Split(channelString, ",")
|
channels := strings.Split(channelString, ",")
|
||||||
for _, chname := range channels {
|
for _, chname := range channels {
|
||||||
server.channels.Join(target, chname, "", true, rb)
|
err := server.channels.Join(target, chname, "", true, rb)
|
||||||
|
if err != nil {
|
||||||
|
sendJoinError(client, chname, rb, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
11
irc/types.go
11
irc/types.go
@ -28,6 +28,17 @@ func (clients ClientSet) Has(client *Client) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StringSet map[string]empty
|
||||||
|
|
||||||
|
func (s StringSet) Has(str string) bool {
|
||||||
|
_, ok := s[str]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s StringSet) Add(str string) {
|
||||||
|
s[str] = empty{}
|
||||||
|
}
|
||||||
|
|
||||||
// MemberSet is a set of members with modes.
|
// MemberSet is a set of members with modes.
|
||||||
type MemberSet map[*Client]*modes.ModeSet
|
type MemberSet map[*Client]*modes.ModeSet
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user