From 02a4aaf5835d88eb2facf7ea4182df5271af5818 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Tue, 19 Jun 2018 04:03:40 -0400 Subject: [PATCH] some tweaks to account/channel unregistration --- irc/chanserv.go | 22 +++++++++++++--------- irc/modes.go | 9 ++++++--- irc/nickserv.go | 37 +++++++++++++++++++++++++------------ oragono.yaml | 2 +- 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/irc/chanserv.go b/irc/chanserv.go index 4d83f82c..aac72967 100644 --- a/irc/chanserv.go +++ b/irc/chanserv.go @@ -10,6 +10,7 @@ import ( "sort" "strconv" "strings" + "time" "github.com/goshuirc/irc-go/ircfmt" "github.com/oragono/oragono/irc/modes" @@ -287,15 +288,10 @@ func csUnregisterHandler(server *Server, client *Client, command, params string, } info := channel.ExportRegistration(0) - // verification code is the crc32 of the name, plus the registration time - var codeInput bytes.Buffer - codeInput.WriteString(info.Name) - codeInput.WriteString(strconv.FormatInt(info.RegisteredAt.Unix(), 16)) - expectedCode := int(crc32.ChecksumIEEE(codeInput.Bytes())) - receivedCode, err := strconv.Atoi(verificationCode) - if err != nil || expectedCode != receivedCode { - csNotice(rb, client.t("$bWarning:$b Unregistering this channel will remove all stored channel attributes.")) - csNotice(rb, fmt.Sprintf(client.t("To confirm channel unregistration, type: /CS UNREGISTER %s %d"), channelKey, expectedCode)) + expectedCode := unregisterConfirmationCode(info.Name, info.RegisteredAt) + if expectedCode != verificationCode { + csNotice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this channel will remove all stored channel attributes.$b"))) + csNotice(rb, fmt.Sprintf(client.t("To confirm channel unregistration, type: /CS UNREGISTER %s %s"), channelKey, expectedCode)) return } @@ -303,3 +299,11 @@ func csUnregisterHandler(server *Server, client *Client, command, params string, go server.channelRegistry.Delete(channelKey, info) csNotice(rb, fmt.Sprintf(client.t("Channel %s is now unregistered"), channelKey)) } + +// deterministically generates a confirmation code for unregistering a channel / account +func unregisterConfirmationCode(name string, registeredAt time.Time) (code string) { + var codeInput bytes.Buffer + codeInput.WriteString(name) + codeInput.WriteString(strconv.FormatInt(registeredAt.Unix(), 16)) + return strconv.Itoa(int(crc32.ChecksumIEEE(codeInput.Bytes()))) +} diff --git a/irc/modes.go b/irc/modes.go index 265f118d..a6f67cd6 100644 --- a/irc/modes.go +++ b/irc/modes.go @@ -255,7 +255,10 @@ func umodeGreaterThan(l modes.Mode, r modes.Mode) bool { // ProcessAccountToUmodeChange processes Add/Remove/List operations for channel persistent usermodes. func (channel *Channel) ProcessAccountToUmodeChange(client *Client, change modes.ModeChange) (results []modes.ModeChange, err error) { - umodeGEQ := func(l modes.Mode, r modes.Mode) bool { + hasPrivsOver := func(l modes.Mode, r modes.Mode) bool { + if l == modes.ChannelAdmin { + return umodeGreaterThan(l, r) + } return l == r || umodeGreaterThan(l, r) } @@ -275,9 +278,9 @@ func (channel *Channel) ProcessAccountToUmodeChange(client *Client, change modes // operators and founders can do anything hasPrivs := isOperChange || (account != "" && account == channel.registeredFounder) // halfop and up can list, and do add/removes at levels <= their own - if change.Op == modes.List && umodeGEQ(clientMode, modes.Halfop) { + if change.Op == modes.List && hasPrivsOver(clientMode, modes.Halfop) { hasPrivs = true - } else if umodeGEQ(clientMode, modes.Halfop) && umodeGEQ(clientMode, targetModeNow) && umodeGEQ(clientMode, targetModeAfter) { + } else if hasPrivsOver(clientMode, modes.Halfop) && hasPrivsOver(clientMode, targetModeNow) && hasPrivsOver(clientMode, targetModeAfter) { hasPrivs = true } if !hasPrivs { diff --git a/irc/nickserv.go b/irc/nickserv.go index 9c396372..fda9669d 100644 --- a/irc/nickserv.go +++ b/irc/nickserv.go @@ -7,6 +7,7 @@ import ( "fmt" "strings" + "github.com/goshuirc/irc-go/ircfmt" "github.com/oragono/oragono/irc/utils" ) @@ -98,10 +99,12 @@ SADROP forcibly de-links the given nickname from the attached user account.`, }, "unregister": { handler: nsUnregisterHandler, - help: `Syntax: $bUNREGISTER [username]$b + help: `Syntax: $bUNREGISTER [code]$b -UNREGISTER lets you delete your user account (or the given one, if you're an -IRC operator with the correct permissions).`, +UNREGISTER lets you delete your user account (or someone else's, if you're an +IRC operator with the correct permissions). To prevent accidental +unregistrations, a verification code is required; invoking the command without +a code will display the necessary code.`, helpShort: `$bUNREGISTER$b lets you delete your user account.`, }, "verify": { @@ -316,7 +319,7 @@ func nsRegisterHandler(server *Server, client *Client, command, params string, r } func nsUnregisterHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) { - username, _ := utils.ExtractParam(params) + username, verificationCode := utils.ExtractParam(params) if !server.AccountConfig().Registration.Enabled { nsNotice(rb, client.t("Account registration has been disabled")) @@ -324,22 +327,32 @@ func nsUnregisterHandler(server *Server, client *Client, command, params string, } if username == "" { - username = client.Account() - } - if username == "" { - nsNotice(rb, client.t("You're not logged into an account")) + nsNotice(rb, client.t("You must specify an account")) return } - cfname, err := CasefoldName(username) - if err != nil { - nsNotice(rb, client.t("Invalid username")) + + 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 } - if !(cfname == client.Account() || client.HasRoleCapabs("unregister")) { + + cfname, _ := CasefoldName(username) + if !(cfname == client.Account() || client.HasRoleCapabs("accreg")) { nsNotice(rb, client.t("Insufficient oper privs")) return } + expectedCode := unregisterConfirmationCode(account.Name, account.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 %s %s"), cfname, expectedCode)) + return + } + if cfname == client.Account() { client.server.accounts.Logout(client) } diff --git a/oragono.yaml b/oragono.yaml index e027ca91..5ea4ffdd 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -280,7 +280,7 @@ oper-classes: capabilities: - "oper:rehash" - "oper:die" - - "unregister" + - "accreg" - "sajoin" - "samode" - "vhosts"