From 6019ed1e2925ef9d9f1a0350898d0f0c02c8d1fc Mon Sep 17 00:00:00 2001 From: Alex Jaspersen Date: Mon, 4 May 2020 00:51:39 +0000 Subject: [PATCH 1/3] Add ChanServ and NickServ LIST commands. These commands search the registered nicknames/channels for ones matching the provided regex, or return the entire list. Only operators with chanreg (for ChanServ) or accreg (for NickServ) capabilities can use LIST. --- irc/accounts.go | 11 +++++++++++ irc/chanserv.go | 39 +++++++++++++++++++++++++++++++++++++++ irc/nickserv.go | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/irc/accounts.go b/irc/accounts.go index 68a8b7a6..cb47a97a 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -1047,6 +1047,17 @@ func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName s return err } +func (am *AccountManager) AllNicks() []string { + am.RLock() + defer am.RUnlock() + + nicks := make([]string, 0, len(am.nickToAccount)) + for nick := range am.nickToAccount { + nicks = append(nicks, nick) + } + return nicks +} + func (am *AccountManager) LoadAccount(accountName string) (result ClientAccount, err error) { casefoldedAccount, err := CasefoldName(accountName) if err != nil { diff --git a/irc/chanserv.go b/irc/chanserv.go index 2a08d313..e34ab4c1 100644 --- a/irc/chanserv.go +++ b/irc/chanserv.go @@ -5,6 +5,7 @@ package irc import ( "fmt" + "regexp" "sort" "strings" "time" @@ -126,6 +127,16 @@ set using PURGE.`, capabs: []string{"chanreg"}, minParams: 1, }, + "list": { + handler: csListHandler, + help: `Syntax: $bLIST [regex]$b + +LIST returns the list of registered channels, which match the given regex. +If no regex is provided, all registered channels are returned.`, + helpShort: `$bLIST$b searches the list of registered channels.`, + capabs: []string{"chanreg"}, + minParams: 0, + }, "info": { handler: csInfoHandler, help: `Syntax: $INFO #channel$b @@ -559,6 +570,34 @@ func csUnpurgeHandler(server *Server, client *Client, command string, params []s } } +func csListHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { + if !client.HasRoleCapabs("chanreg") { + csNotice(rb, client.t("Insufficient privileges")) + return + } + + var searchRegex *regexp.Regexp + if len(params) > 0 { + var err error + searchRegex, err = regexp.Compile(params[0]) + if err != nil { + csNotice(rb, client.t("Invalid regex")) + return + } + } + + csNotice(rb, ircfmt.Unescape(client.t("*** $bChanServ LIST$b ***"))) + + channels := server.channelRegistry.AllChannels() + for _, channel := range channels { + if searchRegex == nil || searchRegex.MatchString(channel) { + csNotice(rb, fmt.Sprintf(" %s", channel)) + } + } + + csNotice(rb, ircfmt.Unescape(client.t("*** $bEnd of ChanServ LIST$b ***"))) +} + func csInfoHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { chname, err := CasefoldChannel(params[0]) if err != nil { diff --git a/irc/nickserv.go b/irc/nickserv.go index 1f52a5df..6b73642e 100644 --- a/irc/nickserv.go +++ b/irc/nickserv.go @@ -5,6 +5,7 @@ package irc import ( "fmt" + "regexp" "strconv" "strings" "time" @@ -97,6 +98,17 @@ certfp (your client certificate) if a password is not given.`, enabled: servCmdRequiresAuthEnabled, minParams: 1, }, + "list": { + handler: nsListHandler, + help: `Syntax: $bLIST [regex]$b + +LIST returns the list of registered nicknames, which match the given regex. +If no regex is provided, all registered nicknames are returned.`, + helpShort: `$bLIST$b searches the list of registered nicknames.`, + enabled: servCmdRequiresAuthEnabled, + capabs: []string{"accreg"}, + minParams: 0, + }, "info": { handler: nsInfoHandler, help: `Syntax: $bINFO [username]$b @@ -681,6 +693,34 @@ func nsIdentifyHandler(server *Server, client *Client, command string, params [] } } +func nsListHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { + if !client.HasRoleCapabs("accreg") { + nsNotice(rb, client.t("Insufficient privileges")) + return + } + + var searchRegex *regexp.Regexp + if len(params) > 0 { + var err error + searchRegex, err = regexp.Compile(params[0]) + if err != nil { + nsNotice(rb, client.t("Invalid regex")) + return + } + } + + nsNotice(rb, ircfmt.Unescape(client.t("*** $bNickServ LIST$b ***"))) + + nicks := server.accounts.AllNicks() + for _, nick := range nicks { + if searchRegex == nil || searchRegex.MatchString(nick) { + nsNotice(rb, fmt.Sprintf(" %s", nick)) + } + } + + nsNotice(rb, ircfmt.Unescape(client.t("*** $bEnd of NickServ LIST$b ***"))) +} + func nsInfoHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { if !server.Config().Accounts.AuthenticationEnabled && !client.HasRoleCapabs("accreg") { nsNotice(rb, client.t("This command has been disabled by the server administrators")) From 73bea0168df06875a0bb81e4560e2892c22ae96a Mon Sep 17 00:00:00 2001 From: Alex Jaspersen Date: Mon, 4 May 2020 02:14:55 +0000 Subject: [PATCH 2/3] Read account names and additional nicknames from the db to avoid casefolding issues. --- irc/accounts.go | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/irc/accounts.go b/irc/accounts.go index cb47a97a..d5cc962f 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -7,6 +7,7 @@ import ( "bytes" "encoding/json" "fmt" + "sort" "strconv" "strings" "sync" @@ -1047,15 +1048,37 @@ func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName s return err } -func (am *AccountManager) AllNicks() []string { - am.RLock() - defer am.RUnlock() +// AllNicks returns the uncasefolded nicknames for all accounts, including additional (grouped) nicks. +func (am *AccountManager) AllNicks() (result []string) { + // Account names + accountNamePrefix := fmt.Sprintf(keyAccountName, "") + am.server.store.View(func(tx *buntdb.Tx) error { + return tx.AscendGreaterOrEqual("", accountNamePrefix, func(key, value string) bool { + if !strings.HasPrefix(key, accountNamePrefix) { + return false + } + result = append(result, value) + return true + }) + }) - nicks := make([]string, 0, len(am.nickToAccount)) - for nick := range am.nickToAccount { - nicks = append(nicks, nick) - } - return nicks + // Additional nicknames + accountAdditionalNicksPrefix := fmt.Sprintf(keyAccountAdditionalNicks, "") + am.server.store.View(func(tx *buntdb.Tx) error { + return tx.AscendGreaterOrEqual("", accountAdditionalNicksPrefix, func(key, value string) bool { + if !strings.HasPrefix(key, accountAdditionalNicksPrefix) { + return false + } + additionalNicks := unmarshalReservedNicks(value) + for _, additionalNick := range additionalNicks { + result = append(result, additionalNick) + } + return true + }) + }) + + sort.Strings(result) + return } func (am *AccountManager) LoadAccount(accountName string) (result ClientAccount, err error) { From b363a01a6f27ac8f081cbcaf4a23f1a1384e4ea1 Mon Sep 17 00:00:00 2001 From: Alex Jaspersen Date: Mon, 4 May 2020 02:20:28 +0000 Subject: [PATCH 3/3] Read accounts and grouped nicks in same transaction. --- irc/accounts.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/irc/accounts.go b/irc/accounts.go index d5cc962f..99e1239a 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -1050,21 +1050,23 @@ func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName s // AllNicks returns the uncasefolded nicknames for all accounts, including additional (grouped) nicks. func (am *AccountManager) AllNicks() (result []string) { - // Account names accountNamePrefix := fmt.Sprintf(keyAccountName, "") + accountAdditionalNicksPrefix := fmt.Sprintf(keyAccountAdditionalNicks, "") + am.server.store.View(func(tx *buntdb.Tx) error { - return tx.AscendGreaterOrEqual("", accountNamePrefix, func(key, value string) bool { + // Account names + err := tx.AscendGreaterOrEqual("", accountNamePrefix, func(key, value string) bool { if !strings.HasPrefix(key, accountNamePrefix) { return false } result = append(result, value) return true }) - }) + if err != nil { + return err + } - // Additional nicknames - accountAdditionalNicksPrefix := fmt.Sprintf(keyAccountAdditionalNicks, "") - am.server.store.View(func(tx *buntdb.Tx) error { + // Additional nicks return tx.AscendGreaterOrEqual("", accountAdditionalNicksPrefix, func(key, value string) bool { if !strings.HasPrefix(key, accountAdditionalNicksPrefix) { return false