From cc6be14c1d9026098966284f09e3dc986f639d82 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 4 Feb 2021 15:26:03 -0500 Subject: [PATCH] fix #1507 Registered channels should be eagerly created on startup, and should remain (and be visible in LIST) even when they have no members. --- irc/channel.go | 41 ++++++++++++++++++++++-------- irc/channelmanager.go | 59 ++++++++++++++++++++++++++++++++++++------- irc/handlers.go | 5 ++-- 3 files changed, 82 insertions(+), 23 deletions(-) diff --git a/irc/channel.go b/irc/channel.go index 583e87d8..88e42582 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -210,8 +210,6 @@ func (channel *Channel) MarkDirty(dirtyBits uint) { // ChannelManager's lock (that way, no one can join and make the channel dirty again // between this method exiting and the actual deletion). func (channel *Channel) IsClean() bool { - config := channel.server.Config() - if !channel.writerSemaphore.TryAcquire() { // a database write (which may fail) is in progress, the channel cannot be cleaned up return false @@ -223,13 +221,8 @@ func (channel *Channel) IsClean() bool { if len(channel.members) != 0 { return false } - if channel.registeredFounder == "" { - return true - } - // a registered channel must be fully written to the DB, - // and not set to ephemeral history (#704) - return channel.dirtyBits == 0 && - channelHistoryStatus(config, true, channel.settings.History) != HistoryEphemeral + // see #1507 and #704 among others; registered channels should never be removed + return channel.registeredFounder == "" } func (channel *Channel) wakeWriter() { @@ -793,7 +786,7 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp return joinErr, "" } - client.server.logger.Debug("join", fmt.Sprintf("%s joined channel %s", details.nick, chname)) + client.server.logger.Debug("channels", fmt.Sprintf("%s joined channel %s", details.nick, chname)) givenMode := func() (givenMode modes.Mode) { channel.joinPartMutex.Lock() @@ -1033,7 +1026,7 @@ func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer) }, details.account) } - client.server.logger.Debug("part", fmt.Sprintf("%s left channel %s", details.nick, chname)) + client.server.logger.Debug("channels", fmt.Sprintf("%s left channel %s", details.nick, chname)) } // Resume is called after a successful global resume to: @@ -1539,6 +1532,32 @@ func (channel *Channel) Kick(client *Client, target *Client, comment string, rb channel.Quit(target) } +// handle a purge: kick everyone off the channel, clean up all the pointers between +// *Channel and *Client +func (channel *Channel) Purge(source string) { + if source == "" { + source = channel.server.name + } + + channel.stateMutex.Lock() + chname := channel.name + members := channel.membersCache + channel.membersCache = nil + channel.members = make(MemberSet) + // TODO try to prevent Purge racing against (pending) Join? + channel.stateMutex.Unlock() + + now := time.Now().UTC() + for _, member := range members { + tnick := member.Nick() + msgid := utils.GenerateSecretToken() + for _, session := range member.Sessions() { + session.sendFromClientInternal(false, now, msgid, source, "*", nil, "KICK", chname, tnick, member.t("This channel has been purged by the server administrators and cannot be used")) + } + member.removeChannel(channel) + } +} + // Invite invites the given client to the channel, if the inviter can do so. func (channel *Channel) Invite(invitee *Client, inviter *Client, rb *ResponseBuffer) { channel.stateMutex.RLock() diff --git a/irc/channelmanager.go b/irc/channelmanager.go index 43b92505..a2b75b26 100644 --- a/irc/channelmanager.go +++ b/irc/channelmanager.go @@ -39,9 +39,9 @@ func (cm *ChannelManager) Initialize(server *Server) { cm.chansSkeletons = make(utils.StringSet) cm.server = server - cm.loadRegisteredChannels(server.Config()) // purging should work even if registration is disabled cm.purgedChannels = cm.server.channelRegistry.PurgedChannels() + cm.loadRegisteredChannels(server.Config()) } func (cm *ChannelManager) loadRegisteredChannels(config *Config) { @@ -49,23 +49,48 @@ func (cm *ChannelManager) loadRegisteredChannels(config *Config) { return } + var newChannels []*Channel + var collisions []string + defer func() { + for _, ch := range newChannels { + ch.EnsureLoaded() + cm.server.logger.Debug("channels", "initialized registered channel", ch.Name()) + } + for _, collision := range collisions { + cm.server.logger.Warning("channels", "registered channel collides with existing channel", collision) + } + }() + rawNames := cm.server.channelRegistry.AllChannels() - registeredChannels := make(utils.StringSet, len(rawNames)) - registeredSkeletons := make(utils.StringSet, len(rawNames)) + + cm.Lock() + defer cm.Unlock() + + cm.registeredChannels = make(utils.StringSet, len(rawNames)) + cm.registeredSkeletons = make(utils.StringSet, len(rawNames)) for _, name := range rawNames { cfname, err := CasefoldChannel(name) if err == nil { - registeredChannels.Add(cfname) + cm.registeredChannels.Add(cfname) } skeleton, err := Skeleton(name) if err == nil { - registeredSkeletons.Add(skeleton) + cm.registeredSkeletons.Add(skeleton) + } + + if !cm.purgedChannels.Has(cfname) { + if _, ok := cm.chans[cfname]; !ok { + ch := NewChannel(cm.server, name, cfname, true) + cm.chans[cfname] = &channelManagerEntry{ + channel: ch, + pendingJoins: 0, + } + newChannels = append(newChannels, ch) + } else { + collisions = append(collisions, name) + } } } - cm.Lock() - defer cm.Unlock() - cm.registeredChannels = registeredChannels - cm.registeredSkeletons = registeredSkeletons } // Get returns an existing channel with name equivalent to `name`, or nil @@ -366,12 +391,28 @@ func (cm *ChannelManager) Purge(chname string, record ChannelPurgeRecord) (err e if err != nil { return errInvalidChannelName } + skel, err := Skeleton(chname) + if err != nil { + return errInvalidChannelName + } cm.Lock() cm.purgedChannels.Add(chname) + entry := cm.chans[chname] + if entry != nil { + delete(cm.chans, chname) + if entry.channel.Founder() != "" { + delete(cm.registeredSkeletons, skel) + } else { + delete(cm.chansSkeletons, skel) + } + } cm.Unlock() cm.server.channelRegistry.PurgeChannel(chname, record) + if entry != nil { + entry.channel.Purge("") + } return nil } diff --git a/irc/handlers.go b/irc/handlers.go index b71e336a..2c3bcf74 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -1614,9 +1614,8 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp nick := client.Nick() rplList := func(channel *Channel) { - if members, name, topic := channel.listData(); members != 0 { - rb.Add(nil, client.server.name, RPL_LIST, nick, name, strconv.Itoa(members), topic) - } + members, name, topic := channel.listData() + rb.Add(nil, client.server.name, RPL_LIST, nick, name, strconv.Itoa(members), topic) } clientIsOp := client.HasMode(modes.Operator)