From 33630766899df5aea347e6105723be0ac7f84ea0 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Fri, 20 Mar 2020 12:34:46 -0400 Subject: [PATCH] fix #793 --- irc/accounts.go | 24 +++++++++++++++---- irc/errors.go | 1 + irc/handlers.go | 2 +- irc/nickserv.go | 62 +++++++++++++++++++++++++++++++++++-------------- 4 files changed, 66 insertions(+), 23 deletions(-) diff --git a/irc/accounts.go b/irc/accounts.go index 80ad1d39..509196d4 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -24,6 +24,7 @@ import ( const ( keyAccountExists = "account.exists %s" keyAccountVerified = "account.verified %s" + keyAccountUnregistered = "account.unregistered %s" keyAccountCallback = "account.callback %s" keyAccountVerificationCode = "account.verificationcode %s" keyAccountName = "account.name %s" // stores the 'preferred name' of the account, not casemapped @@ -363,6 +364,7 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames } accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount) + unregisteredKey := fmt.Sprintf(keyAccountUnregistered, casefoldedAccount) accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount) callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount) registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount) @@ -401,7 +403,11 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames } return am.server.store.Update(func(tx *buntdb.Tx) error { - _, err := am.loadRawAccount(tx, casefoldedAccount) + if _, err := tx.Get(unregisteredKey); err == nil { + return errAccountAlreadyUnregistered + } + + _, err = am.loadRawAccount(tx, casefoldedAccount) if err != errAccountDoesNotExist { return errAccountAlreadyRegistered } @@ -432,7 +438,7 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames code, err := am.dispatchCallback(client, casefoldedAccount, callbackNamespace, callbackValue) if err != nil { - am.Unregister(casefoldedAccount) + am.Unregister(casefoldedAccount, true) return errCallbackFailed } else { return am.server.store.Update(func(tx *buntdb.Tx) error { @@ -1063,7 +1069,7 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string return } -func (am *AccountManager) Unregister(account string) error { +func (am *AccountManager) Unregister(account string, erase bool) error { config := am.server.Config() casefoldedAccount, err := CasefoldName(account) if err != nil { @@ -1084,6 +1090,7 @@ func (am *AccountManager) Unregister(account string) error { channelsKey := fmt.Sprintf(keyAccountChannels, casefoldedAccount) joinedChannelsKey := fmt.Sprintf(keyAccountJoinedChannels, casefoldedAccount) lastSeenKey := fmt.Sprintf(keyAccountLastSeen, casefoldedAccount) + unregisteredKey := fmt.Sprintf(keyAccountUnregistered, casefoldedAccount) var clients []*Client @@ -1104,6 +1111,13 @@ func (am *AccountManager) Unregister(account string) error { var accountName string var channelsStr string am.server.store.Update(func(tx *buntdb.Tx) error { + if erase { + tx.Delete(unregisteredKey) + } else { + if _, err := tx.Get(accountKey); err == nil { + tx.Set(unregisteredKey, "1", nil) + } + } tx.Delete(accountKey) accountName, _ = tx.Get(accountNameKey) tx.Delete(accountNameKey) @@ -1129,7 +1143,7 @@ func (am *AccountManager) Unregister(account string) error { if err == nil { var creds AccountCredentials - if err = json.Unmarshal([]byte(credText), &creds); err == nil { + if err := json.Unmarshal([]byte(credText), &creds); err == nil { for _, cert := range creds.Certfps { certFPKey := fmt.Sprintf(keyCertToAccount, cert) am.server.store.Update(func(tx *buntdb.Tx) error { @@ -1169,7 +1183,7 @@ func (am *AccountManager) Unregister(account string) error { } } - if err != nil { + if err != nil && !erase { return errAccountDoesNotExist } diff --git a/irc/errors.go b/irc/errors.go index 54faecd2..030884ba 100644 --- a/irc/errors.go +++ b/irc/errors.go @@ -13,6 +13,7 @@ import ( // Runtime Errors var ( errAccountAlreadyRegistered = errors.New(`Account already exists`) + errAccountAlreadyUnregistered = errors.New(`That account name was registered previously and can't be reused`) errAccountAlreadyVerified = errors.New(`Account is already verified`) errAccountCantDropPrimaryNick = errors.New("Can't unreserve primary nickname") errAccountCreation = errors.New("Account could not be created") diff --git a/irc/handlers.go b/irc/handlers.go index b49b5159..c25b95e4 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -66,7 +66,7 @@ func registrationErrorToMessageAndCode(err error) (message, code string) { case errAccountBadPassphrase: code = "REG_INVALID_CREDENTIAL" message = err.Error() - case errAccountAlreadyRegistered, errAccountAlreadyVerified: + case errAccountAlreadyRegistered, errAccountAlreadyVerified, errAccountAlreadyUnregistered: message = err.Error() case errAccountCreation, errAccountMustHoldNick, errAccountBadPassphrase, errCertfpAlreadyExists, errFeatureDisabled: message = err.Error() diff --git a/irc/nickserv.go b/irc/nickserv.go index 166c7b37..ac699512 100644 --- a/irc/nickserv.go +++ b/irc/nickserv.go @@ -164,6 +164,20 @@ a code will display the necessary code.`, enabled: servCmdRequiresAuthEnabled, minParams: 1, }, + "erase": { + handler: nsUnregisterHandler, + help: `Syntax: $bERASE [code]$b + +ERASE deletes all records of an account, allowing it to be re-registered. +This should be used with caution, because it violates an expectation that +account names are permanent identifiers. Typically, UNREGISTER should be +used instead. A confirmation code is required; invoking the command +without a code will display the necessary code.`, + helpShort: `$bERASE$b erases all records of an account, allowing reuse.`, + enabled: servCmdRequiresAuthEnabled, + capabs: []string{"accreg"}, + minParams: 1, + }, "verify": { handler: nsVerifyHandler, help: `Syntax: $bVERIFY $b @@ -815,6 +829,8 @@ func nsSaregisterHandler(server *Server, client *Client, command string, params } func nsUnregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { + erase := command == "erase" + username := params[0] var verificationCode string if len(params) > 1 { @@ -826,39 +842,51 @@ func nsUnregisterHandler(server *Server, client *Client, command string, params return } - account, err := server.accounts.LoadAccount(username) - if err == errAccountDoesNotExist { - nsNotice(rb, client.t("Invalid account name")) - return - } else if err != nil { - nsNotice(rb, client.t("Internal error")) - return + var accountName string + var registeredAt time.Time + if erase { + // account may not be in a loadable state, e.g., if it was unregistered + accountName = username + } else { + account, err := server.accounts.LoadAccount(username) + if err == errAccountDoesNotExist { + nsNotice(rb, client.t("Invalid account name")) + return + } else if err != nil { + nsNotice(rb, client.t("Internal error")) + return + } + accountName = account.Name + registeredAt = account.RegisteredAt } - cfname, _ := CasefoldName(username) - if !(cfname == client.Account() || client.HasRoleCapabs("accreg")) { + if !(accountName == client.AccountName() || client.HasRoleCapabs("accreg")) { nsNotice(rb, client.t("Insufficient oper privs")) return } - expectedCode := utils.ConfirmationCode(account.Name, account.RegisteredAt) + expectedCode := utils.ConfirmationCode(accountName, registeredAt) if expectedCode != verificationCode { - nsNotice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this account will remove its stored privileges.$b"))) - nsNotice(rb, fmt.Sprintf(client.t("To confirm account unregistration, type: /NS UNREGISTER %[1]s %[2]s"), cfname, expectedCode)) + if erase { + nsNotice(rb, ircfmt.Unescape(client.t("$bWarning: erasing this account will allow it to be re-registered; consider UNREGISTER instead.$b"))) + nsNotice(rb, fmt.Sprintf(client.t("To confirm account erase, type: /NS ERASE %[1]s %[2]s"), accountName, expectedCode)) + } else { + nsNotice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this account will remove its stored privileges.$b"))) + nsNotice(rb, fmt.Sprintf(client.t("To confirm account unregistration, type: /NS UNREGISTER %[1]s %[2]s"), accountName, expectedCode)) + } return } - err = server.accounts.Unregister(cfname) + err := server.accounts.Unregister(accountName, erase) if err == errAccountDoesNotExist { nsNotice(rb, client.t(err.Error())) } else if err != nil { nsNotice(rb, client.t("Error while unregistering account")) } else { - nsNotice(rb, fmt.Sprintf(client.t("Successfully unregistered account %s"), cfname)) - server.logger.Info("accounts", "client", client.Nick(), "unregistered account", cfname) + nsNotice(rb, fmt.Sprintf(client.t("Successfully unregistered account %s"), accountName)) + server.logger.Info("accounts", "client", client.Nick(), "unregistered account", accountName) + client.server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] unregistered account $c[grey][$r%s$c[grey]]"), client.NickMaskString(), accountName)) } - - client.server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] unregistered account $c[grey][$r%s$c[grey]]"), client.NickMaskString(), account.Name)) } func nsVerifyHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {