From 31f386f5a9dd16dff6bd1380232d83f2da4698cc Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 4 Jun 2018 05:02:22 -0400 Subject: [PATCH 1/2] add CHANSERV UNREGISTER --- irc/channel.go | 11 ++++++ irc/channelreg.go | 14 ++++++++ irc/chanserv.go | 50 +++++++++++++++++++++++--- irc/getters.go | 2 +- irc/server.go | 92 +++++++++++++++++++++++------------------------ 5 files changed, 115 insertions(+), 54 deletions(-) diff --git a/irc/channel.go b/irc/channel.go index 1699a2f9..8c45e5fa 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -157,6 +157,17 @@ func (channel *Channel) SetRegistered(founder string) error { return nil } +// SetUnregistered deletes the channel's registration information. +func (channel *Channel) SetUnregistered() { + channel.stateMutex.Lock() + defer channel.stateMutex.Unlock() + + channel.registeredFounder = "" + var zeroTime time.Time + channel.registeredTime = zeroTime + channel.accountToUMode = make(map[string]modes.Mode) +} + // IsRegistered returns whether the channel is registered. func (channel *Channel) IsRegistered() bool { channel.stateMutex.RLock() diff --git a/irc/channelreg.go b/irc/channelreg.go index 932995c5..6dca4bc7 100644 --- a/irc/channelreg.go +++ b/irc/channelreg.go @@ -201,6 +201,20 @@ func (reg *ChannelRegistry) LoadChannel(nameCasefolded string) (info *Registered return info } +func (reg *ChannelRegistry) Delete(casefoldedName string, info RegisteredChannel) { + if !reg.server.ChannelRegistrationEnabled() { + return + } + + reg.Lock() + defer reg.Unlock() + + reg.server.store.Update(func(tx *buntdb.Tx) error { + reg.deleteChannel(tx, casefoldedName, info) + return nil + }) +} + // Rename handles the persistence part of a channel rename: the channel is // persisted under its new name, and the old name is cleaned up if necessary. func (reg *ChannelRegistry) Rename(channel *Channel, casefoldedOldName string) { diff --git a/irc/chanserv.go b/irc/chanserv.go index 3decf726..3a082535 100644 --- a/irc/chanserv.go +++ b/irc/chanserv.go @@ -22,6 +22,10 @@ To see in-depth help for a specific ChanServ command, try: Here are the commands you can use: %s` +func chanregEnabled(server *Server) bool { + return server.ChannelRegistrationEnabled() +} + var ( chanservCommands = map[string]*serviceCommand{ "op": { @@ -32,6 +36,7 @@ OP makes the given nickname, or yourself, a channel admin. You can only use this command if you're the founder of the channel.`, helpShort: `$bOP$b makes the given user (or yourself) a channel admin.`, authRequired: true, + enabled: chanregEnabled, }, "register": { handler: csRegisterHandler, @@ -42,6 +47,15 @@ given admin privs on it. Modes set on the channel and the topic will also be remembered.`, helpShort: `$bREGISTER$b lets you own a given channel.`, authRequired: true, + enabled: chanregEnabled, + }, + "unregister": { + handler: csUnregisterHandler, + help: `Syntax: $bUNREGISTER #channel$b + +UNREGISTER deletes a channel registration, allowing someone else to claim it.`, + helpShort: `$bUNREGISTER$b deletes a channel registration.`, + enabled: chanregEnabled, }, "amode": { handler: csAmodeHandler, @@ -53,6 +67,7 @@ account the +o operator mode every time they join #channel. To list current accounts and modes, use $bAMODE #channel$b. Note that users are always referenced by their registered account names, not their nicknames.`, helpShort: `$bAMODE$b modifies persistent mode settings for channel members.`, + enabled: chanregEnabled, }, } ) @@ -197,11 +212,6 @@ func csOpHandler(server *Server, client *Client, command, params string, rb *Res } func csRegisterHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) { - if !server.channelRegistrationEnabled { - csNotice(rb, client.t("Channel registration is not enabled")) - return - } - channelName := strings.TrimSpace(params) if channelName == "" { csNotice(rb, ircfmt.Unescape(client.t("Syntax: $bREGISTER #channel$b"))) @@ -246,3 +256,33 @@ func csRegisterHandler(server *Server, client *Client, command, params string, r } } } + +func csUnregisterHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) { + channelName := strings.TrimSpace(params) + channelKey, err := CasefoldChannel(channelName) + if channelKey == "" || err != nil { + csNotice(rb, client.t("Channel name is not valid")) + return + } + + channel := server.channels.Get(channelKey) + if channel == nil { + csNotice(rb, client.t("No such channel")) + return + } + + hasPrivs := client.HasRoleCapabs("chanreg") + if !hasPrivs { + founder := channel.Founder() + hasPrivs = founder != "" && founder == client.Account() + } + if !hasPrivs { + csNotice(rb, client.t("Insufficient privileges")) + return + } + + info := channel.ExportRegistration(0) + channel.SetUnregistered() + go server.channelRegistry.Delete(channelKey, info) + csNotice(rb, fmt.Sprintf(client.t("Channel %s is now unregistered"), channelKey)) +} diff --git a/irc/getters.go b/irc/getters.go index 1972f53d..55a72944 100644 --- a/irc/getters.go +++ b/irc/getters.go @@ -62,7 +62,7 @@ func (server *Server) DefaultChannelModes() modes.Modes { func (server *Server) ChannelRegistrationEnabled() bool { server.configurableStateMutex.RLock() defer server.configurableStateMutex.RUnlock() - return server.channelRegistrationEnabled + return server.config.Channels.Registration.Enabled } func (server *Server) AccountConfig() *AccountConfig { diff --git a/irc/server.go b/irc/server.go index 7413e208..335c6534 100644 --- a/irc/server.go +++ b/irc/server.go @@ -87,51 +87,50 @@ type ListenerWrapper struct { // Server is the main Oragono server. type Server struct { - accounts *AccountManager - batches *BatchManager - channelRegistrationEnabled bool - channels *ChannelManager - channelRegistry *ChannelRegistry - checkIdent bool - clients *ClientManager - config *Config - configFilename string - configurableStateMutex sync.RWMutex // tier 1; generic protection for server state modified by rehash() - connectionLimiter *connection_limits.Limiter - connectionThrottler *connection_limits.Throttler - ctime time.Time - defaultChannelModes modes.Modes - dlines *DLineManager - loggingRawIO bool - isupport *isupport.List - klines *KLineManager - languages *languages.Manager - limits Limits - listeners map[string]*ListenerWrapper - logger *logger.Manager - maxSendQBytes uint32 - monitorManager *MonitorManager - motdLines []string - name string - nameCasefolded string - networkName string - operators map[string]*Oper - operclasses map[string]*OperClass - password []byte - passwords *passwd.SaltedManager - recoverFromErrors bool - rehashMutex sync.Mutex // tier 4 - rehashSignal chan os.Signal - pprofServer *http.Server - proxyAllowedFrom []string - signals chan os.Signal - snomasks *SnoManager - store *buntdb.DB - stsEnabled bool - webirc []webircConfig - whoWas *WhoWasList - stats *Stats - semaphores *ServerSemaphores + accounts *AccountManager + batches *BatchManager + channels *ChannelManager + channelRegistry *ChannelRegistry + checkIdent bool + clients *ClientManager + config *Config + configFilename string + configurableStateMutex sync.RWMutex // tier 1; generic protection for server state modified by rehash() + connectionLimiter *connection_limits.Limiter + connectionThrottler *connection_limits.Throttler + ctime time.Time + defaultChannelModes modes.Modes + dlines *DLineManager + loggingRawIO bool + isupport *isupport.List + klines *KLineManager + languages *languages.Manager + limits Limits + listeners map[string]*ListenerWrapper + logger *logger.Manager + maxSendQBytes uint32 + monitorManager *MonitorManager + motdLines []string + name string + nameCasefolded string + networkName string + operators map[string]*Oper + operclasses map[string]*OperClass + password []byte + passwords *passwd.SaltedManager + recoverFromErrors bool + rehashMutex sync.Mutex // tier 4 + rehashSignal chan os.Signal + pprofServer *http.Server + proxyAllowedFrom []string + signals chan os.Signal + snomasks *SnoManager + store *buntdb.DB + stsEnabled bool + webirc []webircConfig + whoWas *WhoWasList + stats *Stats + semaphores *ServerSemaphores } var ( @@ -955,9 +954,6 @@ func (server *Server) applyConfig(config *Config, initial bool) error { server.operators = opers server.checkIdent = config.Server.CheckIdent - // registration - server.channelRegistrationEnabled = config.Channels.Registration.Enabled - server.defaultChannelModes = ParseDefaultChannelModes(config) server.configurableStateMutex.Unlock() From c3b66b52365cec835e6a4b5df376971e133bff75 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Tue, 5 Jun 2018 05:23:36 -0400 Subject: [PATCH 2/2] add a verification code to CS UNREGISTER --- irc/chanserv.go | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/irc/chanserv.go b/irc/chanserv.go index 3a082535..4d83f82c 100644 --- a/irc/chanserv.go +++ b/irc/chanserv.go @@ -4,8 +4,11 @@ package irc import ( + "bytes" "fmt" + "hash/crc32" "sort" + "strconv" "strings" "github.com/goshuirc/irc-go/ircfmt" @@ -51,9 +54,11 @@ remembered.`, }, "unregister": { handler: csUnregisterHandler, - help: `Syntax: $bUNREGISTER #channel$b + help: `Syntax: $bUNREGISTER #channel [code]$b -UNREGISTER deletes a channel registration, allowing someone else to claim it.`, +UNREGISTER deletes a channel registration, allowing someone else to claim it. +To prevent accidental unregistrations, a verification code is required; +invoking the command without a code will display the necessary code.`, helpShort: `$bUNREGISTER$b deletes a channel registration.`, enabled: chanregEnabled, }, @@ -258,7 +263,7 @@ func csRegisterHandler(server *Server, client *Client, command, params string, r } func csUnregisterHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) { - channelName := strings.TrimSpace(params) + channelName, verificationCode := utils.ExtractParam(params) channelKey, err := CasefoldChannel(channelName) if channelKey == "" || err != nil { csNotice(rb, client.t("Channel name is not valid")) @@ -282,6 +287,18 @@ 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)) + return + } + channel.SetUnregistered() go server.channelRegistry.Delete(channelKey, info) csNotice(rb, fmt.Sprintf(client.t("Channel %s is now unregistered"), channelKey))