3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-22 03:49:27 +01:00
ergo/irc/channelmanager.go

483 lines
12 KiB
Go
Raw Normal View History

2017-10-30 10:21:47 +01:00
// Copyright (c) 2017 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license
package irc
import (
"sort"
2017-10-30 10:21:47 +01:00
"sync"
2020-08-05 03:46:16 +02:00
2021-05-25 06:34:38 +02:00
"github.com/ergochat/ergo/irc/utils"
2017-10-30 10:21:47 +01:00
)
type channelManagerEntry struct {
channel *Channel
// this is a refcount for joins, so we can avoid a race where we incorrectly
// think the channel is empty (without holding a lock across the entire Channel.Join()
// call)
pendingJoins int
2019-12-17 19:21:26 +01:00
skeleton string
2017-10-30 10:21:47 +01:00
}
// ChannelManager keeps track of all the channels on the server,
// providing synchronization for creation of new channels on first join,
// cleanup of empty channels on last part, and renames.
type ChannelManager struct {
2019-12-17 19:21:26 +01:00
sync.RWMutex // tier 2
// chans is the main data structure, mapping casefolded name -> *Channel
chans map[string]*channelManagerEntry
2022-03-30 06:44:51 +02:00
chansSkeletons utils.HashSet[string] // skeletons of *unregistered* chans
registeredChannels utils.HashSet[string] // casefolds of registered chans
registeredSkeletons utils.HashSet[string] // skeletons of registered chans
purgedChannels utils.HashSet[string] // casefolds of purged chans
2019-12-17 19:21:26 +01:00
server *Server
2017-10-30 10:21:47 +01:00
}
// NewChannelManager returns a new ChannelManager.
2019-03-12 00:24:45 +01:00
func (cm *ChannelManager) Initialize(server *Server) {
cm.chans = make(map[string]*channelManagerEntry)
2022-03-30 06:44:51 +02:00
cm.chansSkeletons = make(utils.HashSet[string])
2019-03-12 00:24:45 +01:00
cm.server = server
2019-12-17 19:21:26 +01:00
// purging should work even if registration is disabled
cm.purgedChannels = cm.server.channelRegistry.PurgedChannels()
cm.loadRegisteredChannels(server.Config())
2017-10-30 10:21:47 +01:00
}
func (cm *ChannelManager) loadRegisteredChannels(config *Config) {
if !config.Channels.Registration.Enabled {
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)
}
}()
2019-12-17 19:21:26 +01:00
rawNames := cm.server.channelRegistry.AllChannels()
cm.Lock()
defer cm.Unlock()
2022-03-30 06:44:51 +02:00
cm.registeredChannels = make(utils.HashSet[string], len(rawNames))
cm.registeredSkeletons = make(utils.HashSet[string], len(rawNames))
2019-12-17 19:21:26 +01:00
for _, name := range rawNames {
cfname, err := CasefoldChannel(name)
if err == nil {
cm.registeredChannels.Add(cfname)
2019-12-17 19:21:26 +01:00
}
skeleton, err := Skeleton(name)
if err == nil {
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)
}
2019-12-17 19:21:26 +01:00
}
}
2019-03-12 00:24:45 +01:00
}
2017-10-30 10:21:47 +01:00
// Get returns an existing channel with name equivalent to `name`, or nil
2019-03-12 00:24:45 +01:00
func (cm *ChannelManager) Get(name string) (channel *Channel) {
2017-10-30 10:21:47 +01:00
name, err := CasefoldChannel(name)
if err == nil {
cm.RLock()
defer cm.RUnlock()
entry := cm.chans[name]
2019-03-12 00:24:45 +01:00
// if the channel is still loading, pretend we don't have it
if entry != nil && entry.channel.IsLoaded() {
return entry.channel
}
2017-10-30 10:21:47 +01:00
}
return nil
}
// Join causes `client` to join the channel named `name`, creating it if necessary.
func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin bool, rb *ResponseBuffer) (err error, forward string) {
2017-10-30 10:21:47 +01:00
server := client.server
casefoldedName, err := CasefoldChannel(name)
2019-12-17 19:21:26 +01:00
skeleton, skerr := Skeleton(name)
if err != nil || skerr != nil || len(casefoldedName) > server.Config().Limits.ChannelLen {
return errNoSuchChannel, ""
2017-10-30 10:21:47 +01:00
}
channel, err, newChannel := func() (*Channel, error, bool) {
var newChannel bool
2019-05-30 11:33:59 +02:00
cm.Lock()
defer cm.Unlock()
2019-12-17 19:21:26 +01:00
if cm.purgedChannels.Has(casefoldedName) {
return nil, errChannelPurged, false
}
2019-05-30 11:33:59 +02:00
entry := cm.chans[casefoldedName]
if entry == nil {
2019-12-17 19:21:26 +01:00
registered := cm.registeredChannels.Has(casefoldedName)
2019-05-30 11:33:59 +02:00
// enforce OpOnlyCreation
if !registered && server.Config().Channels.OpOnlyCreation &&
!(isSajoin || client.HasRoleCapabs("chanreg")) {
return nil, errInsufficientPrivs, false
2019-12-17 19:21:26 +01:00
}
// enforce confusables
if !registered && (cm.chansSkeletons.Has(skeleton) || cm.registeredSkeletons.Has(skeleton)) {
return nil, errConfusableIdentifier, false
2019-05-30 11:33:59 +02:00
}
entry = &channelManagerEntry{
2019-12-17 19:21:26 +01:00
channel: NewChannel(server, name, casefoldedName, registered),
2019-05-30 11:33:59 +02:00
pendingJoins: 0,
}
2019-12-17 19:21:26 +01:00
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
}
2019-05-30 11:33:59 +02:00
cm.chans[casefoldedName] = entry
newChannel = true
2017-10-30 10:21:47 +01:00
}
2019-05-30 11:33:59 +02:00
entry.pendingJoins += 1
return entry.channel, nil, newChannel
2019-05-30 11:33:59 +02:00
}()
2019-12-17 19:21:26 +01:00
if err != nil {
return err, ""
2017-10-30 10:21:47 +01:00
}
2019-03-12 00:24:45 +01:00
channel.EnsureLoaded()
err, forward = channel.Join(client, key, isSajoin || newChannel, rb)
2017-10-30 10:21:47 +01:00
2019-03-12 00:24:45 +01:00
cm.maybeCleanup(channel, true)
2017-10-30 10:21:47 +01:00
return
2017-10-30 10:21:47 +01:00
}
func (cm *ChannelManager) maybeCleanup(channel *Channel, afterJoin bool) {
2017-10-30 10:21:47 +01:00
cm.Lock()
defer cm.Unlock()
2019-12-17 19:21:26 +01:00
cfname := channel.NameCasefolded()
entry := cm.chans[cfname]
if entry == nil || entry.channel != channel {
2017-10-30 10:21:47 +01:00
return
}
cm.maybeCleanupInternal(cfname, entry, afterJoin)
}
func (cm *ChannelManager) maybeCleanupInternal(cfname string, entry *channelManagerEntry, afterJoin bool) {
2017-10-30 10:21:47 +01:00
if afterJoin {
entry.pendingJoins -= 1
}
2019-03-12 00:24:45 +01:00
if entry.pendingJoins == 0 && entry.channel.IsClean() {
2019-12-17 19:21:26 +01:00
delete(cm.chans, cfname)
if entry.skeleton != "" {
delete(cm.chansSkeletons, entry.skeleton)
}
2017-10-30 10:21:47 +01:00
}
}
// Part parts `client` from the channel named `name`, deleting it if it's empty.
2018-02-05 15:21:08 +01:00
func (cm *ChannelManager) Part(client *Client, name string, message string, rb *ResponseBuffer) error {
2019-03-12 00:24:45 +01:00
var channel *Channel
2017-10-30 10:21:47 +01:00
casefoldedName, err := CasefoldChannel(name)
if err != nil {
2018-02-03 13:03:36 +01:00
return errNoSuchChannel
2017-10-30 10:21:47 +01:00
}
cm.RLock()
entry := cm.chans[casefoldedName]
2019-03-12 00:24:45 +01:00
if entry != nil {
channel = entry.channel
}
2017-10-30 10:21:47 +01:00
cm.RUnlock()
2019-03-12 00:24:45 +01:00
if channel == nil {
2018-02-03 13:03:36 +01:00
return errNoSuchChannel
2017-10-30 10:21:47 +01:00
}
2019-03-12 00:24:45 +01:00
channel.Part(client, message, rb)
2017-10-30 10:21:47 +01:00
return nil
}
func (cm *ChannelManager) Cleanup(channel *Channel) {
cm.maybeCleanup(channel, false)
}
2019-03-12 00:24:45 +01:00
func (cm *ChannelManager) SetRegistered(channelName string, account string) (err error) {
2020-07-08 11:32:14 +02:00
if cm.server.Defcon() <= 4 {
return errFeatureDisabled
}
2019-03-12 00:24:45 +01:00
var channel *Channel
cfname, err := CasefoldChannel(channelName)
if err != nil {
return err
}
var entry *channelManagerEntry
defer func() {
if err == nil && channel != nil {
// registration was successful: make the database reflect it
2020-03-02 07:46:22 +01:00
err = channel.Store(IncludeAllAttrs)
2019-03-12 00:24:45 +01:00
}
}()
cm.Lock()
defer cm.Unlock()
entry = cm.chans[cfname]
if entry == nil {
return errNoSuchChannel
}
channel = entry.channel
err = channel.SetRegistered(account)
if err != nil {
return err
}
// transfer the skeleton from chansSkeletons to registeredSkeletons
skeleton := entry.skeleton
delete(cm.chansSkeletons, skeleton)
entry.skeleton = ""
cm.chans[cfname] = entry
2019-12-17 19:21:26 +01:00
cm.registeredChannels.Add(cfname)
cm.registeredSkeletons.Add(skeleton)
2019-03-12 00:24:45 +01:00
return nil
}
func (cm *ChannelManager) SetUnregistered(channelName string, account string) (err error) {
cfname, err := CasefoldChannel(channelName)
if err != nil {
return err
}
2020-03-20 19:40:14 +01:00
info, err := cm.server.channelRegistry.LoadChannel(cfname)
if err != nil {
return err
}
if info.Founder != account {
return errChannelNotOwnedByAccount
}
2019-03-12 00:24:45 +01:00
defer func() {
if err == nil {
err = cm.server.channelRegistry.Delete(info)
}
}()
cm.Lock()
defer cm.Unlock()
entry := cm.chans[cfname]
2020-03-20 19:40:14 +01:00
if entry != nil {
entry.channel.SetUnregistered(account)
delete(cm.registeredChannels, cfname)
// transfer the skeleton from registeredSkeletons to chansSkeletons
2020-03-20 19:40:14 +01:00
if skel, err := Skeleton(entry.channel.Name()); err == nil {
delete(cm.registeredSkeletons, skel)
cm.chansSkeletons.Add(skel)
entry.skeleton = skel
cm.chans[cfname] = entry
2020-03-20 19:40:14 +01:00
}
// #1619: if the channel has 0 members and was only being retained
// because it was registered, clean it up:
cm.maybeCleanupInternal(cfname, entry, false)
2020-01-12 17:20:30 +01:00
}
2019-03-12 00:24:45 +01:00
return nil
}
2017-10-30 10:21:47 +01:00
// Rename renames a channel (but does not notify the members)
2019-12-17 19:21:26 +01:00
func (cm *ChannelManager) Rename(name string, newName string) (err error) {
oldCfname, err := CasefoldChannel(name)
2017-10-30 10:21:47 +01:00
if err != nil {
2018-02-03 13:03:36 +01:00
return errNoSuchChannel
2017-10-30 10:21:47 +01:00
}
2019-12-17 19:21:26 +01:00
newCfname, err := CasefoldChannel(newName)
if err != nil {
return errInvalidChannelName
}
newSkeleton, err := Skeleton(newName)
2017-10-30 10:21:47 +01:00
if err != nil {
2018-02-03 13:03:36 +01:00
return errInvalidChannelName
2017-10-30 10:21:47 +01:00
}
2019-03-12 00:24:45 +01:00
var channel *Channel
var info RegisteredChannel
defer func() {
if channel != nil && info.Founder != "" {
2020-03-02 07:46:22 +01:00
channel.Store(IncludeAllAttrs)
if oldCfname != newCfname {
// we just flushed the channel under its new name, therefore this delete
// cannot be overwritten by a write to the old name:
cm.server.channelRegistry.Delete(info)
}
2019-03-12 00:24:45 +01:00
}
}()
2017-10-30 10:21:47 +01:00
cm.Lock()
defer cm.Unlock()
entry := cm.chans[oldCfname]
2019-12-17 19:21:26 +01:00
if entry == nil || !entry.channel.IsLoaded() {
2018-02-03 13:03:36 +01:00
return errNoSuchChannel
2017-10-30 10:21:47 +01:00
}
2019-03-12 00:24:45 +01:00
channel = entry.channel
info = channel.ExportRegistration(IncludeInitial)
2019-12-17 19:21:26 +01:00
registered := info.Founder != ""
oldSkeleton, err := Skeleton(info.Name)
if err != nil {
return errNoSuchChannel // ugh
}
if newCfname != oldCfname {
if cm.chans[newCfname] != nil || cm.registeredChannels.Has(newCfname) {
return errChannelNameInUse
}
}
if oldSkeleton != newSkeleton {
if cm.chansSkeletons.Has(newSkeleton) || cm.registeredSkeletons.Has(newSkeleton) {
return errConfusableIdentifier
}
}
delete(cm.chans, oldCfname)
if !registered {
entry.skeleton = newSkeleton
}
2019-12-17 19:21:26 +01:00
cm.chans[newCfname] = entry
if registered {
delete(cm.registeredChannels, oldCfname)
2019-12-17 19:21:26 +01:00
cm.registeredChannels.Add(newCfname)
delete(cm.registeredSkeletons, oldSkeleton)
2019-12-17 19:21:26 +01:00
cm.registeredSkeletons.Add(newSkeleton)
} else {
delete(cm.chansSkeletons, oldSkeleton)
2019-12-17 19:21:26 +01:00
cm.chansSkeletons.Add(newSkeleton)
}
2019-12-17 19:21:26 +01:00
entry.channel.Rename(newName, newCfname)
2017-10-30 10:21:47 +01:00
return nil
}
// Len returns the number of channels
func (cm *ChannelManager) Len() int {
cm.RLock()
defer cm.RUnlock()
return len(cm.chans)
}
// Channels returns a slice containing all current channels
func (cm *ChannelManager) Channels() (result []*Channel) {
cm.RLock()
defer cm.RUnlock()
2019-03-12 00:24:45 +01:00
result = make([]*Channel, 0, len(cm.chans))
2017-10-30 10:21:47 +01:00
for _, entry := range cm.chans {
2019-03-12 00:24:45 +01:00
if entry.channel.IsLoaded() {
result = append(result, entry.channel)
}
2017-10-30 10:21:47 +01:00
}
return
}
// Purge marks a channel as purged.
func (cm *ChannelManager) Purge(chname string, record ChannelPurgeRecord) (err error) {
chname, err = CasefoldChannel(chname)
if err != nil {
return errInvalidChannelName
}
skel, err := Skeleton(chname)
if err != nil {
return errInvalidChannelName
}
cm.Lock()
2019-12-17 19:21:26 +01:00
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
}
// IsPurged queries whether a channel is purged.
func (cm *ChannelManager) IsPurged(chname string) (result bool) {
chname, err := CasefoldChannel(chname)
if err != nil {
return false
}
2020-01-12 17:20:30 +01:00
cm.RLock()
2019-12-17 19:21:26 +01:00
result = cm.purgedChannels.Has(chname)
2020-01-12 17:20:30 +01:00
cm.RUnlock()
return
}
// Unpurge deletes a channel's purged status.
func (cm *ChannelManager) Unpurge(chname string) (err error) {
chname, err = CasefoldChannel(chname)
if err != nil {
return errNoSuchChannel
}
cm.Lock()
2019-12-17 19:21:26 +01:00
found := cm.purgedChannels.Has(chname)
delete(cm.purgedChannels, chname)
cm.Unlock()
cm.server.channelRegistry.UnpurgeChannel(chname)
if !found {
return errNoSuchChannel
}
return nil
}
func (cm *ChannelManager) ListPurged() (result []string) {
cm.RLock()
result = make([]string, 0, len(cm.purgedChannels))
for c := range cm.purgedChannels {
result = append(result, c)
}
cm.RUnlock()
sort.Strings(result)
return
}
2021-04-07 11:40:39 +02:00
func (cm *ChannelManager) UnfoldName(cfname string) (result string) {
cm.RLock()
entry := cm.chans[cfname]
cm.RUnlock()
if entry != nil && entry.channel.IsLoaded() {
return entry.channel.Name()
}
return cfname
}