From 6f8711da3bf3e8e9304bd40cf8fab12e1acc64ed Mon Sep 17 00:00:00 2001 From: Conrad Lukawski Date: Mon, 6 Jul 2020 04:08:04 -0400 Subject: [PATCH] Persist realname for always-on clients --- irc/accounts.go | 38 ++++++++++++++++++++++++++++++++++++-- irc/client.go | 7 ++++++- irc/client_lookup_set.go | 7 ------- irc/getters.go | 4 ++++ irc/handlers.go | 5 +++++ 5 files changed, 51 insertions(+), 10 deletions(-) diff --git a/irc/accounts.go b/irc/accounts.go index 40a40022..739fb8f5 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -39,7 +39,8 @@ const ( keyAccountChannels = "account.channels %s" // channels registered to the account keyAccountJoinedChannels = "account.joinedto %s" // channels a persistent client has joined 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 keyVHostQueueAcctToId = "vhostQueue %s" vhostRequestIdx = "vhostQueue" @@ -127,7 +128,13 @@ func (am *AccountManager) createAlwaysOnClients(config *Config) { account, err := am.LoadAccount(accountName) if err == nil && account.Verified && persistenceEnabled(config.Accounts.Multiclient.AlwaysOn, account.Settings.AlwaysOn) { - am.server.AddAlwaysOnClient(account, am.loadChannels(accountName), am.loadLastSeen(accountName), am.loadModes(accountName)) + am.server.AddAlwaysOnClient( + account, + am.loadChannels(accountName), + am.loadLastSeen(accountName), + am.loadModes(accountName), + am.loadRealname(accountName), + ) } } } @@ -650,6 +657,30 @@ func (am *AccountManager) loadLastSeen(account string) (lastSeen map[string]time return } +func (am *AccountManager) saveRealname(account string, realname string) { + key := fmt.Sprintf(keyAccountRealname, account) + am.server.store.Update(func(tx *buntdb.Tx) error { + if realname != "" { + tx.Set(key, realname, nil) + } else { + tx.Delete(key) + } + return nil + }) +} + +func (am *AccountManager) loadRealname(account string) (realname string) { + key := fmt.Sprintf(keyAccountRealname, account) + am.server.store.Update(func(tx *buntdb.Tx) error { + realname, _ = tx.Get(key) + return nil + }) + if realname == "" { + return "" + } + return +} + func (am *AccountManager) addRemoveCertfp(account, certfp string, add bool, hasPrivs bool) (err error) { certfp, err = utils.NormalizeCertfp(certfp) if err != nil { @@ -874,6 +905,9 @@ func (am *AccountManager) Verify(client *Client, account string, code string) er am.server.RandomlyRename(currentClient) } } + if client.AlwaysOn() { + client.markDirty(IncludeRealname) + } return nil } diff --git a/irc/client.go b/irc/client.go index 9f772ca3..08602193 100644 --- a/irc/client.go +++ b/irc/client.go @@ -365,7 +365,7 @@ func (server *Server) RunClient(conn IRCConn) { client.run(session) } -func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSeen map[string]time.Time, uModes modes.Modes) { +func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSeen map[string]time.Time, uModes modes.Modes, realname string) { now := time.Now().UTC() config := server.Config() if lastSeen == nil && account.Settings.AutoreplayMissed { @@ -386,6 +386,7 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, realIP: utils.IPv4LoopbackAddress, alwaysOn: true, + realname: realname, } client.SetMode(modes.TLS, true) @@ -1707,6 +1708,7 @@ const ( IncludeChannels uint = 1 << iota IncludeLastSeen IncludeUserModes + IncludeRealname ) func (client *Client) markDirty(dirtyBits uint) { @@ -1778,6 +1780,9 @@ func (client *Client) performWrite(additionalDirtyBits uint) { } client.server.accounts.saveModes(account, uModes) } + if (dirtyBits & IncludeRealname) != 0 { + client.server.accounts.saveRealname(account, client.realname) + } } // Blocking store; see Channel.Store and Socket.BlockingWrite diff --git a/irc/client_lookup_set.go b/irc/client_lookup_set.go index 26544453..59d235ae 100644 --- a/irc/client_lookup_set.go +++ b/irc/client_lookup_set.go @@ -122,7 +122,6 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick accountName := client.accountName settings := client.accountSettings registered := client.registered - realname := client.realname client.stateMutex.RUnlock() // recompute always-on status, because client.alwaysOn is not set for unregistered clients @@ -225,12 +224,6 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick client.server.stats.AddRegistered(invisible, operator) } session.autoreplayMissedSince = lastSeen - // XXX SetNames only changes names if they are unset, so the realname change only - // takes effect on first attach to an always-on client (good), but the user/ident - // change is always a no-op (bad). we could make user/ident act the same way as - // realname, but then we'd have to send CHGHOST and i don't want to deal with that - // for performance reasons - currentClient.SetNames("user", realname, true) // successful reattach! return newNick, nil, back } else if currentClient == client && currentClient.Nick() == newNick { diff --git a/irc/getters.go b/irc/getters.go index c7f207f0..c40af649 100644 --- a/irc/getters.go +++ b/irc/getters.go @@ -373,7 +373,11 @@ func (client *Client) SetMode(mode modes.Mode, on bool) bool { func (client *Client) SetRealname(realname string) { client.stateMutex.Lock() client.realname = realname + alwaysOn := client.alwaysOn client.stateMutex.Unlock() + if alwaysOn { + client.markDirty(IncludeRealname) + } } func (client *Client) Channels() (result []*Channel) { diff --git a/irc/handlers.go b/irc/handlers.go index eaa83e27..6fd6a6ba 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -2514,6 +2514,11 @@ func sceneHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res // SETNAME func setnameHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { realname := msg.Params[0] + if realname == "" { + rb.Add(nil, server.name, "FAIL", "SETNAME", "INVALID_REALNAME", client.t("Realname is not valid")) + return false + } + client.SetRealname(realname) details := client.Details()