diff --git a/irc/client.go b/irc/client.go index e7cb33a1..4fb37046 100644 --- a/irc/client.go +++ b/irc/client.go @@ -1085,7 +1085,8 @@ var ( func (session *Session) SendRawMessage(message ircmsg.IrcMessage, blocking bool) error { // use dumb hack to force the last param to be a trailing param if required var usedTrailingHack bool - if commandsThatMustUseTrailing[message.Command] && len(message.Params) > 0 { + config := session.client.server.Config() + if config.Server.Compatibility.forceTrailing && commandsThatMustUseTrailing[message.Command] && len(message.Params) > 0 { lastParam := message.Params[len(message.Params)-1] // to force trailing, we ensure the final param contains a space if strings.IndexByte(lastParam, ' ') == -1 { diff --git a/irc/config.go b/irc/config.go index c585601b..c79d43cd 100644 --- a/irc/config.go +++ b/irc/config.go @@ -286,9 +286,14 @@ type Config struct { WebIRC []webircConfig `yaml:"webirc"` MaxSendQString string `yaml:"max-sendq"` MaxSendQBytes int - AllowPlaintextResume bool `yaml:"allow-plaintext-resume"` - ConnectionLimiter connection_limits.LimiterConfig `yaml:"connection-limits"` - ConnectionThrottler connection_limits.ThrottlerConfig `yaml:"connection-throttling"` + AllowPlaintextResume bool `yaml:"allow-plaintext-resume"` + Compatibility struct { + ForceTrailing *bool `yaml:"force-trailing"` + forceTrailing bool + SendUnprefixedSasl bool `yaml:"send-unprefixed-sasl"` + } + ConnectionLimiter connection_limits.LimiterConfig `yaml:"connection-limits"` + ConnectionThrottler connection_limits.ThrottlerConfig `yaml:"connection-throttling"` } Languages struct { @@ -698,6 +703,13 @@ func LoadConfig(filename string) (config *Config, err error) { config.Channels.Registration.MaxChannelsPerAccount = 15 } + forceTrailingPtr := config.Server.Compatibility.ForceTrailing + if forceTrailingPtr != nil { + config.Server.Compatibility.forceTrailing = *forceTrailingPtr + } else { + config.Server.Compatibility.forceTrailing = true + } + // in the current implementation, we disable history by creating a history buffer // with zero capacity. but the `enabled` config option MUST be respected regardless // of this detail diff --git a/irc/getters.go b/irc/getters.go index ac3e5124..7ee029ec 100644 --- a/irc/getters.go +++ b/irc/getters.go @@ -4,7 +4,9 @@ package irc import ( + "sync/atomic" "time" + "unsafe" "github.com/oragono/oragono/irc/isupport" "github.com/oragono/oragono/irc/languages" @@ -12,10 +14,11 @@ import ( ) func (server *Server) Config() (config *Config) { - server.configurableStateMutex.RLock() - config = server.config - server.configurableStateMutex.RUnlock() - return + return (*Config)(atomic.LoadPointer(&server.config)) +} + +func (server *Server) SetConfig(config *Config) { + atomic.StorePointer(&server.config, unsafe.Pointer(config)) } func (server *Server) ISupport() *isupport.List { @@ -53,9 +56,7 @@ func (server *Server) GetOperator(name string) (oper *Oper) { if err != nil { return } - server.configurableStateMutex.RLock() - defer server.configurableStateMutex.RUnlock() - return server.config.operators[name] + return server.Config().operators[name] } func (server *Server) Languages() (lm *languages.Manager) { diff --git a/irc/handlers.go b/irc/handlers.go index de67ccfd..d47d4569 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -298,7 +298,9 @@ func accVerifyHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb // AUTHENTICATE [||*] func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { + config := server.Config() details := client.Details() + if details.account != "" { rb.Add(nil, server.name, ERR_SASLALREADY, details.nick, client.t("You're already logged into an account")) return false @@ -321,7 +323,14 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage, if mechanismIsEnabled { client.saslInProgress = true client.saslMechanism = mechanism - rb.Add(nil, server.name, "AUTHENTICATE", "+") + if !config.Server.Compatibility.SendUnprefixedSasl { + // normal behavior + rb.Add(nil, server.name, "AUTHENTICATE", "+") + } else { + // gross hack: send a raw message to ensure no tags or prefix + rb.Flush(true) + rb.session.SendRawMessage(ircmsg.MakeMessage(nil, "", "AUTHENTICATE", "+"), true) + } } else { rb.Add(nil, server.name, ERR_SASLFAIL, details.nick, client.t("SASL authentication failed")) } diff --git a/irc/server.go b/irc/server.go index fc628098..c9a0b1ce 100644 --- a/irc/server.go +++ b/irc/server.go @@ -19,6 +19,7 @@ import ( "sync" "syscall" "time" + "unsafe" "github.com/goshuirc/irc-go/ircfmt" "github.com/oragono/oragono/irc/caps" @@ -65,7 +66,7 @@ type Server struct { channels ChannelManager channelRegistry ChannelRegistry clients ClientManager - config *Config + config unsafe.Pointer configFilename string configurableStateMutex sync.RWMutex // tier 1; generic protection for server state modified by rehash() connectionLimiter *connection_limits.Limiter @@ -602,7 +603,7 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) { return fmt.Errorf("Maximum line length (linelen) cannot be changed after launching the server, rehash aborted") } else if server.name != config.Server.Name { return fmt.Errorf("Server name cannot be changed after launching the server, rehash aborted") - } else if server.config.Datastore.Path != config.Datastore.Path { + } else if server.Config().Datastore.Path != config.Datastore.Path { return fmt.Errorf("Datastore path cannot be changed after launching the server, rehash aborted") } } @@ -776,9 +777,7 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) { server.loadMOTD(config.Server.MOTD, config.Server.MOTDFormatting) // save a pointer to the new config - server.configurableStateMutex.Lock() - server.config = config - server.configurableStateMutex.Unlock() + server.SetConfig(config) server.logger.Info("server", "Using datastore", config.Datastore.Path) if initial { diff --git a/oragono.yaml b/oragono.yaml index dd38c929..0e5e68e5 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -125,6 +125,21 @@ server: # this should be big enough to hold bursts of channel/direct messages max-sendq: 16k + # compatibility with legacy clients + compatibility: + # many clients require that the final parameter of certain messages be an + # RFC1459 trailing parameter, i.e., prefixed with :, whether or not this is + # actually required. this forces Oragono to send those parameters + # as trailings. this is recommended unless you're testing clients for conformance; + # defaults to true when unset for that reason. + force-trailing: true + + # some clients (ZNC 1.6.x and lower, Pidgin 2.12 and lower, Adium) do not + # respond correctly to SASL messages with the server name as a prefix: + # https://github.com/znc/znc/issues/1212 + # this works around that bug, allowing them to use SASL. + send-unprefixed-sasl: true + # maximum number of connections per subnet connection-limits: # whether to enforce connection limits or not