diff --git a/conventional.yaml b/conventional.yaml index cc29d405..eaac8384 100644 --- a/conventional.yaml +++ b/conventional.yaml @@ -134,6 +134,19 @@ server: # if this is true, the motd is escaped using formatting codes like $c, $b, and $i motd-formatting: true + # relaying using the RELAYMSG command + relaying: + # is relaying enabled at all? + enabled: true + + # which character(s) are reserved for relayed nicks? + separators: "/" + + # can channel operators use RELAYMSG in their channels? + # our implementation of RELAYMSG makes it safe for chanops to use without the + # possibility of real users being silently spoofed + available-to-chanops: true + # addresses/CIDRs the PROXY command can be used from # this should be restricted to localhost (127.0.0.1/8, ::1/128, and unix sockets), # unless you have a good reason. you should also add these addresses to the diff --git a/irc/client_lookup_set.go b/irc/client_lookup_set.go index d0d49b1e..d24b5c55 100644 --- a/irc/client_lookup_set.go +++ b/irc/client_lookup_set.go @@ -173,8 +173,13 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick return "", errNicknameInvalid, false } - if strings.Contains(newCfNick, "/") { - return "", errNicknameInvalid, false + config := client.server.Config() + if config.Server.Relaying.Enabled { + for _, char := range config.Server.Relaying.Separators { + if strings.ContainsRune(newCfNick, char) { + return "", errNicknameInvalid, false + } + } } if restrictedCasefoldedNicks[newCfNick] || restrictedSkeletons[newSkeleton] { diff --git a/irc/config.go b/irc/config.go index a65eafe5..09724b19 100644 --- a/irc/config.go +++ b/irc/config.go @@ -499,14 +499,19 @@ type Config struct { CheckIdent bool `yaml:"check-ident"` MOTD string motdLines []string - MOTDFormatting bool `yaml:"motd-formatting"` - ProxyAllowedFrom []string `yaml:"proxy-allowed-from"` - proxyAllowedFromNets []net.IPNet - WebIRC []webircConfig `yaml:"webirc"` - MaxSendQString string `yaml:"max-sendq"` - MaxSendQBytes int - AllowPlaintextResume bool `yaml:"allow-plaintext-resume"` - Compatibility struct { + MOTDFormatting bool `yaml:"motd-formatting"` + Relaying struct { + Enabled bool + Separators string + AvailableToChanops bool `yaml:"available-to-chanops"` + } + ProxyAllowedFrom []string `yaml:"proxy-allowed-from"` + proxyAllowedFromNets []net.IPNet + WebIRC []webircConfig `yaml:"webirc"` + MaxSendQString string `yaml:"max-sendq"` + MaxSendQBytes int + AllowPlaintextResume bool `yaml:"allow-plaintext-resume"` + Compatibility struct { ForceTrailing *bool `yaml:"force-trailing"` forceTrailing bool SendUnprefixedSasl bool `yaml:"send-unprefixed-sasl"` @@ -1068,8 +1073,16 @@ func LoadConfig(filename string) (config *Config, err error) { } config.Server.capValues[caps.Languages] = config.languageManager.CapValue() - // intentionally not configurable - config.Server.capValues[caps.Relaymsg] = "/" + if config.Server.Relaying.Enabled { + for _, char := range protocolBreakingNameCharacters { + if strings.ContainsRune(config.Server.Relaying.Separators, char) { + return nil, fmt.Errorf("Relaying separators cannot include the characters %s", protocolBreakingNameCharacters) + } + } + config.Server.capValues[caps.Relaymsg] = config.Server.Relaying.Separators + } else { + config.Server.supportedCaps.Disable(caps.Relaymsg) + } config.Debug.recoverFromErrors = utils.BoolDefaultTrue(config.Debug.RecoverFromErrors) diff --git a/irc/handlers.go b/irc/handlers.go index 7b5d1655..e1dc4ad1 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -1888,12 +1888,22 @@ func messageHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R break } - if strings.Contains(targetString, "/") { - if histType == history.Privmsg { - rb.Add(nil, server.name, ERR_NOSUCHNICK, client.Nick(), targetString, client.t("Relayed users cannot be sent private messages")) + config := server.Config() + if config.Server.Relaying.Enabled { + var isForRelayClient bool + for _, char := range config.Server.Relaying.Separators { + if strings.ContainsRune(targetString, char) { + isForRelayClient = true + break + } + } + if isForRelayClient { + if histType == history.Privmsg { + rb.Add(nil, server.name, ERR_NOSUCHNICK, client.Nick(), targetString, client.t("Relayed users cannot be sent private messages")) + } + // TAGMSG/NOTICEs are intentionally silently dropped + continue } - // TAGMSG/NOTICEs are intentionally silently dropped - continue } // each target gets distinct msgids @@ -2281,14 +2291,21 @@ func rehashHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re // RELAYMSG : func relaymsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) (result bool) { + config := server.Config() + if !config.Server.Relaying.Enabled { + rb.Add(nil, server.name, "FAIL", "RELAYMSG", "NOT_ENABLED", client.t("Relaying has been disabled")) + return false + } + channel := server.channels.Get(msg.Params[0]) if channel == nil { rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.Nick(), utils.SafeErrorParam(msg.Params[0]), client.t("No such channel")) return false } - if !(channel.ClientIsAtLeast(client, modes.ChannelOperator) || client.HasRoleCapabs("relaymsg-anywhere")) { - rb.Add(nil, server.name, "FAIL", "RELAYMSG", "NOT_PRIVED", client.t("Only channel operators or ircops with the 'relaymsg-anywhere' role can relay messages")) + allowedToRelay := client.HasRoleCapabs("relaymsg-anywhere") || (config.Server.Relaying.AvailableToChanops && channel.ClientIsAtLeast(client, modes.ChannelOperator)) + if !allowedToRelay { + rb.Add(nil, server.name, "FAIL", "RELAYMSG", "NOT_PRIVED", client.t("You cannot relay messages to this channel")) return false } diff --git a/irc/server.go b/irc/server.go index a6f2c168..8b828ab1 100644 --- a/irc/server.go +++ b/irc/server.go @@ -497,6 +497,10 @@ func (server *Server) applyConfig(config *Config) (err error) { return fmt.Errorf("Casemapping cannot be changed after launching the server, rehash aborted") } else if oldConfig.Accounts.Multiclient.AlwaysOn != config.Accounts.Multiclient.AlwaysOn { return fmt.Errorf("Default always-on setting cannot be changed after launching the server, rehash aborted") + } else if oldConfig.Server.Relaying.Enabled != config.Server.Relaying.Enabled { + return fmt.Errorf("Cannot enable or disable relaying after launching the server, rehash aborted") + } else if oldConfig.Server.Relaying.Separators != config.Server.Relaying.Separators { + return fmt.Errorf("Cannot change relaying separators after launching the server, rehash aborted") } } diff --git a/irc/strings.go b/irc/strings.go index 6c8100f6..a80bb08d 100644 --- a/irc/strings.go +++ b/irc/strings.go @@ -19,6 +19,16 @@ import ( const ( precisUTF8MappingToken = "rfc8265" + + // space can't be used + // , is used as a separator + // * is used in mask matching + // ? is used in mask matching + // . denotes a server name + // ! separates nickname from username + // @ separates username from hostname + // : means trailing + protocolBreakingNameCharacters = " ,*?.!@:" ) var ( @@ -132,18 +142,10 @@ func CasefoldName(name string) (string, error) { return "", errStringIsEmpty } - // space can't be used - // , is used as a separator - // * is used in mask matching - // ? is used in mask matching - // . denotes a server name - // ! separates nickname from username - // @ separates username from hostname - // : means trailing // # is a channel prefix // ~&@%+ are channel membership prefixes // - I feel like disallowing - if strings.ContainsAny(lowered, " ,*?.!@:") || strings.ContainsAny(string(lowered[0]), "#~&@%+-") { + if strings.ContainsAny(lowered, protocolBreakingNameCharacters) || strings.ContainsAny(string(lowered[0]), "#~&@%+-") { return "", errInvalidCharacter } diff --git a/oragono.yaml b/oragono.yaml index ba40d8f5..d94f73eb 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -160,6 +160,19 @@ server: # if this is true, the motd is escaped using formatting codes like $c, $b, and $i motd-formatting: true + # relaying using the RELAYMSG command + relaying: + # is relaying enabled at all? + enabled: true + + # which character(s) are reserved for relayed nicks? + separators: "/" + + # can channel operators use RELAYMSG in their channels? + # our implementation of RELAYMSG makes it safe for chanops to use without the + # possibility of real users being silently spoofed + available-to-chanops: true + # addresses/CIDRs the PROXY command can be used from # this should be restricted to localhost (127.0.0.1/8, ::1/128, and unix sockets), # unless you have a good reason. you should also add these addresses to the