This commit is contained in:
Shivaram Lingamneni 2019-12-17 13:21:26 -05:00
parent 07865b8f63
commit c5a81d59ff
6 changed files with 126 additions and 52 deletions

View File

@ -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{

View File

@ -13,36 +13,54 @@ 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,
// providing synchronization for creation of new channels on first join, // providing synchronization for creation of new channels on first join,
// 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 map[string]*channelManagerEntry // chans is the main data structure, mapping casefolded name -> *Channel
registeredChannels map[string]bool chans map[string]*channelManagerEntry
purgedChannels map[string]empty chansSkeletons StringSet // skeletons of *unregistered* chans
server *Server registeredChannels StringSet // casefolds of registered chans
registeredSkeletons StringSet // skeletons of registered chans
purgedChannels StringSet // casefolds of purged chans
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)
}
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, cfnewname) 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()

View File

@ -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)

View File

@ -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`)

View File

@ -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
} }

View File

@ -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