From 33630766899df5aea347e6105723be0ac7f84ea0 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Fri, 20 Mar 2020 12:34:46 -0400 Subject: [PATCH 1/2] 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) { From 811da03ab578f15196bc50557b0678d3adfcef84 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Fri, 20 Mar 2020 14:40:14 -0400 Subject: [PATCH 2/2] fix #889 --- irc/accounts.go | 5 ++++- irc/channelmanager.go | 25 +++++++++++++------------ irc/channelreg.go | 1 + 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/irc/accounts.go b/irc/accounts.go index 509196d4..3edec142 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -1098,7 +1098,10 @@ func (am *AccountManager) Unregister(account string, erase bool) error { // on our way out, unregister all the account's channels and delete them from the db defer func() { for _, channelName := range registeredChannels { - am.server.channels.SetUnregistered(channelName, casefoldedAccount) + err := am.server.channels.SetUnregistered(channelName, casefoldedAccount) + if err != nil { + am.server.logger.Error("internal", "couldn't unregister channel", channelName, err.Error()) + } } }() diff --git a/irc/channelmanager.go b/irc/channelmanager.go index 9312313c..1b32ffaa 100644 --- a/irc/channelmanager.go +++ b/irc/channelmanager.go @@ -226,7 +226,13 @@ func (cm *ChannelManager) SetUnregistered(channelName string, account string) (e return err } - var info RegisteredChannel + info, err := cm.server.channelRegistry.LoadChannel(cfname) + if err != nil { + return err + } + if info.Founder != account { + return errChannelNotOwnedByAccount + } defer func() { if err == nil { @@ -237,17 +243,12 @@ func (cm *ChannelManager) SetUnregistered(channelName string, account string) (e cm.Lock() defer cm.Unlock() entry := cm.chans[cfname] - if entry == nil { - return errNoSuchChannel - } - info = entry.channel.ExportRegistration(0) - if info.Founder != account { - return errChannelNotOwnedByAccount - } - entry.channel.SetUnregistered(account) - delete(cm.registeredChannels, cfname) - if skel, err := Skeleton(entry.channel.Name()); err == nil { - delete(cm.registeredSkeletons, skel) + if entry != nil { + entry.channel.SetUnregistered(account) + delete(cm.registeredChannels, cfname) + if skel, err := Skeleton(entry.channel.Name()); err == nil { + delete(cm.registeredSkeletons, skel) + } } return nil } diff --git a/irc/channelreg.go b/irc/channelreg.go index c1140538..31b68254 100644 --- a/irc/channelreg.go +++ b/irc/channelreg.go @@ -231,6 +231,7 @@ func (reg *ChannelRegistry) LoadChannel(nameCasefolded string) (info RegisteredC info = RegisteredChannel{ Name: name, + NameCasefolded: nameCasefolded, RegisteredAt: time.Unix(regTimeInt, 0).UTC(), Founder: founder, Topic: topic,