From 98e04c10a80f264245d7ece044fae368b8fcc0e9 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sun, 6 Apr 2025 01:41:03 -0400 Subject: [PATCH] fix #2220 (#2240) Allow publishing arbitrary ISUPPORT via the config file --- default.yaml | 7 +++++++ irc/config.go | 7 +++++++ irc/isupport/list.go | 32 ++++++++++++++++++++++++++++---- traditional.yaml | 7 +++++++ 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/default.yaml b/default.yaml index 0078d4ae..000cbd89 100644 --- a/default.yaml +++ b/default.yaml @@ -100,6 +100,7 @@ server: max-connections-per-duration: 64 # strict transport security, to get clients to automagically use TLS + # (irrelevant in the recommended configuration, with no public plaintext listener) sts: # whether to advertise STS # @@ -375,6 +376,12 @@ server: # if you don't want to publicize how popular the server is suppress-lusers: false + # publish additional key-value pairs in ISUPPORT (the 005 numeric). + # keys that collide with a key published by Ergo will be silently ignored. + additional-isupport: + #"draft/FILEHOST": "https://example.com/filehost" + #"draft/bazbat": "" # empty string means no value + # optionally map command alias names to existing ergo commands. most deployments # should ignore this. #command-aliases: diff --git a/irc/config.go b/irc/config.go index 57f9ecd7..bf6c1708 100644 --- a/irc/config.go +++ b/irc/config.go @@ -609,6 +609,7 @@ type Config struct { OverrideServicesHostname string `yaml:"override-services-hostname"` MaxLineLen int `yaml:"max-line-len"` SuppressLusers bool `yaml:"suppress-lusers"` + AdditionalISupport map[string]string `yaml:"additional-isupport"` CommandAliases map[string]string `yaml:"command-aliases"` } @@ -1778,6 +1779,12 @@ func (config *Config) generateISupport() (err error) { } isupport.Add("WHOX", "") + for key, value := range config.Server.AdditionalISupport { + if !isupport.Contains(key) { + isupport.Add(key, value) + } + } + err = isupport.RegenerateCachedReply() return } diff --git a/irc/isupport/list.go b/irc/isupport/list.go index d54df127..2a8dde8e 100644 --- a/irc/isupport/list.go +++ b/irc/isupport/list.go @@ -47,6 +47,12 @@ func (il *List) AddNoValue(name string) { il.Tokens[name] = "" } +// Contains returns whether the list already contains a token +func (il *List) Contains(name string) bool { + _, ok := il.Tokens[name] + return ok +} + // getTokenString gets the appropriate string for a token+value. func getTokenString(name string, value string) string { if len(value) == 0 { @@ -115,16 +121,34 @@ func (il *List) GetDifference(newil *List) [][]string { return replies } +func validateToken(token string) error { + if len(token) == 0 || token[0] == ':' || strings.Contains(token, " ") { + return fmt.Errorf("bad isupport token (cannot be sent as IRC parameter): `%s`", token) + } + + if strings.ContainsAny(token, "\n\r\x00") { + return fmt.Errorf("bad isupport token (contains forbidden octets)") + } + + // technically a token can be maxLastArgLength if it occurs alone, + // but fail it just to be safe + if len(token) >= maxLastArgLength { + return fmt.Errorf("bad isupport token (too long): `%s`", token) + } + + return nil +} + // RegenerateCachedReply regenerates the cached RPL_ISUPPORT reply func (il *List) RegenerateCachedReply() (err error) { var tokens []string for name, value := range il.Tokens { token := getTokenString(name, value) - if token[0] == ':' || strings.Contains(token, " ") { - err = fmt.Errorf("bad isupport token (cannot contain spaces or start with :): %s", token) - continue + if tokenErr := validateToken(token); tokenErr == nil { + tokens = append(tokens, token) + } else { + err = tokenErr } - tokens = append(tokens, token) } // make sure we get a sorted list of tokens, needed for tests and looks nice slices.Sort(tokens) diff --git a/traditional.yaml b/traditional.yaml index 90b53d6d..c4c16e0e 100644 --- a/traditional.yaml +++ b/traditional.yaml @@ -74,6 +74,7 @@ server: max-connections-per-duration: 64 # strict transport security, to get clients to automagically use TLS + # (irrelevant in the recommended configuration, with no public plaintext listener) sts: # whether to advertise STS # @@ -347,6 +348,12 @@ server: # if you don't want to publicize how popular the server is suppress-lusers: false + # publish additional key-value pairs in ISUPPORT (the 005 numeric). + # keys that collide with a key published by Ergo will be silently ignored. + additional-isupport: + #"draft/FILEHOST": "https://example.com/filehost" + #"draft/bazbat": "" # empty string means no value + # optionally map command alias names to existing ergo commands. most deployments # should ignore this. #command-aliases: