3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-12-23 03:02:48 +01:00
Store the channel-user modes of always-on clients along with their
channel memberships, restore them on server startup. This will coexist
alongside /CS AMODE, which autoapplies modes to clients on join regardless
of their always-on status.
This commit is contained in:
Shivaram Lingamneni 2020-12-02 03:56:00 -05:00
parent 3aeac42978
commit 51f279289d
4 changed files with 63 additions and 18 deletions

View File

@ -38,11 +38,13 @@ const (
keyAccountVHost = "account.vhost %s" keyAccountVHost = "account.vhost %s"
keyCertToAccount = "account.creds.certfp %s" keyCertToAccount = "account.creds.certfp %s"
keyAccountChannels = "account.channels %s" // channels registered to the account keyAccountChannels = "account.channels %s" // channels registered to the account
keyAccountJoinedChannels = "account.joinedto %s" // channels a persistent client has joined
keyAccountLastSeen = "account.lastseen %s" keyAccountLastSeen = "account.lastseen %s"
keyAccountModes = "account.modes %s" // user modes for the always-on client as a string keyAccountModes = "account.modes %s" // user modes for the always-on client as a string
keyAccountRealname = "account.realname %s" // client realname stored as string keyAccountRealname = "account.realname %s" // client realname stored as string
keyAccountSuspended = "account.suspended %s" // client realname stored as string keyAccountSuspended = "account.suspended %s" // client realname stored as string
// for an always-on client, a map of channel names they're in to their current modes
// (not to be confused with their amodes, which a non-always-on client can have):
keyAccountChannelToModes = "account.channeltomodes %s"
maxCertfpsPerAccount = 5 maxCertfpsPerAccount = 5
) )
@ -542,24 +544,34 @@ func (am *AccountManager) setPassword(account string, password string, hasPrivs
return err return err
} }
func (am *AccountManager) saveChannels(account string, channels []string) { func (am *AccountManager) saveChannels(account string, channelToModes map[string]string) {
channelsStr := strings.Join(channels, ",") j, err := json.Marshal(channelToModes)
key := fmt.Sprintf(keyAccountJoinedChannels, account) if err != nil {
am.server.logger.Error("internal", "couldn't marshal channel-to-modes", account, err.Error())
return
}
jStr := string(j)
key := fmt.Sprintf(keyAccountChannelToModes, account)
am.server.store.Update(func(tx *buntdb.Tx) error { am.server.store.Update(func(tx *buntdb.Tx) error {
tx.Set(key, channelsStr, nil) tx.Set(key, jStr, nil)
return nil return nil
}) })
} }
func (am *AccountManager) loadChannels(account string) (channels []string) { func (am *AccountManager) loadChannels(account string) (channelToModes map[string]string) {
key := fmt.Sprintf(keyAccountJoinedChannels, account) key := fmt.Sprintf(keyAccountChannelToModes, account)
var channelsStr string var channelsStr string
am.server.store.View(func(tx *buntdb.Tx) error { am.server.store.View(func(tx *buntdb.Tx) error {
channelsStr, _ = tx.Get(key) channelsStr, _ = tx.Get(key)
return nil return nil
}) })
if channelsStr != "" { if channelsStr == "" {
return strings.Split(channelsStr, ",") return nil
}
err := json.Unmarshal([]byte(channelsStr), &channelToModes)
if err != nil {
am.server.logger.Error("internal", "couldn't marshal channel-to-modes", account, err.Error())
return nil
} }
return return
} }
@ -1454,7 +1466,7 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount) settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount) vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
channelsKey := fmt.Sprintf(keyAccountChannels, casefoldedAccount) channelsKey := fmt.Sprintf(keyAccountChannels, casefoldedAccount)
joinedChannelsKey := fmt.Sprintf(keyAccountJoinedChannels, casefoldedAccount) joinedChannelsKey := fmt.Sprintf(keyAccountChannelToModes, casefoldedAccount)
lastSeenKey := fmt.Sprintf(keyAccountLastSeen, casefoldedAccount) lastSeenKey := fmt.Sprintf(keyAccountLastSeen, casefoldedAccount)
unregisteredKey := fmt.Sprintf(keyAccountUnregistered, casefoldedAccount) unregisteredKey := fmt.Sprintf(keyAccountUnregistered, casefoldedAccount)
modesKey := fmt.Sprintf(keyAccountModes, casefoldedAccount) modesKey := fmt.Sprintf(keyAccountModes, casefoldedAccount)

View File

@ -553,6 +553,30 @@ func (channel *Channel) ClientStatus(client *Client) (present bool, cModes modes
return present, modes.AllModes() return present, modes.AllModes()
} }
// helper for persisting channel-user modes for always-on clients;
// return the channel name and all channel-user modes for a client
func (channel *Channel) nameAndModes(client *Client) (chname string, modeStr string) {
channel.stateMutex.RLock()
defer channel.stateMutex.RUnlock()
chname = channel.name
modeStr = channel.members[client].String()
return
}
// overwrite any existing channel-user modes with the stored ones
func (channel *Channel) setModesForClient(client *Client, modeStr string) {
newModes := modes.NewModeSet()
for _, mode := range modeStr {
newModes.SetMode(modes.Mode(mode), true)
}
channel.stateMutex.Lock()
defer channel.stateMutex.Unlock()
if _, ok := channel.members[client]; !ok {
return
}
channel.members[client] = newModes
}
func (channel *Channel) ClientHasPrivsOver(client *Client, target *Client) bool { func (channel *Channel) ClientHasPrivsOver(client *Client, target *Client) bool {
channel.stateMutex.RLock() channel.stateMutex.RLock()
founder := channel.registeredFounder founder := channel.registeredFounder
@ -1383,6 +1407,9 @@ func (channel *Channel) applyModeToMember(client *Client, change modes.ModeChang
if !exists { if !exists {
rb.Add(nil, client.server.name, ERR_USERNOTINCHANNEL, client.Nick(), channel.Name(), client.t("They aren't on that channel")) rb.Add(nil, client.server.name, ERR_USERNOTINCHANNEL, client.Nick(), channel.Name(), client.t("They aren't on that channel"))
} }
if applied {
target.markDirty(IncludeChannels)
}
return return
} }

View File

@ -404,7 +404,7 @@ func (server *Server) RunClient(conn IRCConn) {
client.run(session) client.run(session)
} }
func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSeen map[string]time.Time, uModes modes.Modes, realname string) { func (server *Server) AddAlwaysOnClient(account ClientAccount, channelToModes map[string]string, lastSeen map[string]time.Time, uModes modes.Modes, realname string) {
now := time.Now().UTC() now := time.Now().UTC()
config := server.Config() config := server.Config()
if lastSeen == nil && account.Settings.AutoreplayMissed { if lastSeen == nil && account.Settings.AutoreplayMissed {
@ -463,10 +463,15 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string,
// XXX set this last to avoid confusing SetNick: // XXX set this last to avoid confusing SetNick:
client.registered = true client.registered = true
for _, chname := range chnames { for chname, modeStr := range channelToModes {
// XXX we're using isSajoin=true, to make these joins succeed even without channel key // XXX we're using isSajoin=true, to make these joins succeed even without channel key
// this is *probably* ok as long as the persisted memberships are accurate // this is *probably* ok as long as the persisted memberships are accurate
server.channels.Join(client, chname, "", true, nil) server.channels.Join(client, chname, "", true, nil)
if channel := server.channels.Get(chname); channel != nil {
channel.setModesForClient(client, modeStr)
} else {
server.logger.Error("internal", "could not create channel", chname)
}
} }
if persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) { if persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
@ -1967,11 +1972,12 @@ func (client *Client) performWrite(additionalDirtyBits uint) {
if (dirtyBits & IncludeChannels) != 0 { if (dirtyBits & IncludeChannels) != 0 {
channels := client.Channels() channels := client.Channels()
channelNames := make([]string, len(channels)) channelToModes := make(map[string]string, len(channels))
for i, channel := range channels { for _, channel := range channels {
channelNames[i] = channel.Name() chname, modes := channel.nameAndModes(client)
channelToModes[chname] = modes
} }
client.server.accounts.saveChannels(account, channelNames) client.server.accounts.saveChannels(account, channelToModes)
} }
if (dirtyBits & IncludeLastSeen) != 0 { if (dirtyBits & IncludeLastSeen) != 0 {
client.server.accounts.saveLastSeen(account, client.copyLastSeen()) client.server.accounts.saveLastSeen(account, client.copyLastSeen())

View File

@ -211,9 +211,9 @@ func (client *Client) SetAway(away bool, awayMessage string) (changed bool) {
} }
func (client *Client) AlwaysOn() (alwaysOn bool) { func (client *Client) AlwaysOn() (alwaysOn bool) {
client.stateMutex.Lock() client.stateMutex.RLock()
alwaysOn = client.registered && client.alwaysOn alwaysOn = client.registered && client.alwaysOn
client.stateMutex.Unlock() client.stateMutex.RUnlock()
return return
} }