diff --git a/irc/accounts.go b/irc/accounts.go index 4e782130..4100882e 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -1406,6 +1406,36 @@ func (am *AccountManager) ListSuspended() (result []AccountSuspension) { return } +// renames an account (within very restrictive limits); see #1380 +func (am *AccountManager) Rename(oldName, newName string) (err error) { + accountData, err := am.LoadAccount(oldName) + if err != nil { + return + } + newCfName, err := CasefoldName(newName) + if err != nil { + return errNicknameInvalid + } + if newCfName != accountData.NameCasefolded { + return errInvalidAccountRename + } + key := fmt.Sprintf(keyAccountName, accountData.NameCasefolded) + err = am.server.store.Update(func(tx *buntdb.Tx) error { + tx.Set(key, newName, nil) + return nil + }) + if err != nil { + return err + } + + am.RLock() + defer am.RUnlock() + for _, client := range am.accountToClients[accountData.NameCasefolded] { + client.setAccountName(newName) + } + return nil +} + func (am *AccountManager) Unregister(account string, erase bool) error { config := am.server.Config() casefoldedAccount, err := CasefoldName(account) diff --git a/irc/errors.go b/irc/errors.go index e0d0361f..41228b32 100644 --- a/irc/errors.go +++ b/irc/errors.go @@ -73,6 +73,7 @@ var ( errInviteOnly = errors.New("Cannot join invite-only channel without an invite") errRegisteredOnly = errors.New("Cannot join registered-only channel without an account") errValidEmailRequired = errors.New("A valid email address is required for account registration") + errInvalidAccountRename = errors.New("Account renames can only change the casefolding of the account name") ) // String Errors diff --git a/irc/getters.go b/irc/getters.go index 1672df27..bbc6cf4f 100644 --- a/irc/getters.go +++ b/irc/getters.go @@ -300,6 +300,13 @@ func (client *Client) Login(account ClientAccount) { return } +func (client *Client) setAccountName(name string) { + // XXX this assumes validation elsewhere + client.stateMutex.Lock() + defer client.stateMutex.Unlock() + client.accountName = name +} + func (client *Client) historyCutoff() (cutoff time.Time) { client.stateMutex.Lock() if client.account != "" { diff --git a/irc/nickserv.go b/irc/nickserv.go index 1ebe5d7e..b0544264 100644 --- a/irc/nickserv.go +++ b/irc/nickserv.go @@ -347,6 +347,17 @@ command lists all current suspensions.`, minParams: 1, capabs: []string{"accreg"}, }, + "rename": { + handler: nsRenameHandler, + help: `Syntax: $bRENAME $b + +RENAME allows a server administrator to change the name of an account. +Currently, you can only change the canonical casefolding of an account +(e.g., you can change "Alice" to "alice", but not "Alice" to "Amanda").`, + helpShort: `$bRENAME$b renames an account`, + minParams: 2, + capabs: []string{"accreg"}, + }, } ) @@ -1377,3 +1388,23 @@ func suspensionToString(client *Client, suspension AccountSuspension) (result st } return fmt.Sprintf(client.t("Account %[1]s suspended at %[2]s. Duration: %[3]s. %[4]s"), suspension.AccountName, ts, duration, reason) } + +func nsRenameHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { + oldName, newName := params[0], params[1] + err := server.accounts.Rename(oldName, newName) + + if err != nil { + nsNotice(rb, fmt.Sprintf(client.t("Couldn't rename account: %s"), client.t(err.Error()))) + return + } + + nsNotice(rb, client.t("Successfully renamed account")) + if server.Config().Accounts.NickReservation.ForceNickEqualsAccount { + if curClient := server.clients.Get(oldName); curClient != nil { + renameErr := performNickChange(client.server, client, curClient, nil, newName, rb) + if renameErr != nil && renameErr != errNoop { + nsNotice(rb, fmt.Sprintf(client.t("Warning: could not rename affected client: %v"), err)) + } + } + } +}