diff --git a/default.yaml b/default.yaml index 676f33b4..cb27c5a5 100644 --- a/default.yaml +++ b/default.yaml @@ -300,6 +300,9 @@ server: kill-timeout: 1s # how many scripts are allowed to run at once? 0 for no limit: max-concurrency: 64 + # if true, only check anonymous connections (not logged into an account) + # at the very end of the handshake: + exempt-sasl: false # IP cloaking hides users' IP addresses from other users and from channel admins # (but not from server admins), while still allowing channel admins to ban diff --git a/irc/authscript.go b/irc/authscript.go index bb5dea95..8299afe1 100644 --- a/irc/authscript.go +++ b/irc/authscript.go @@ -84,7 +84,7 @@ type IPScriptOutput struct { Error string `json:"error"` } -func CheckIPBan(sem utils.Semaphore, config ScriptConfig, addr net.IP) (output IPScriptOutput, err error) { +func CheckIPBan(sem utils.Semaphore, config IPCheckScriptConfig, addr net.IP) (output IPScriptOutput, err error) { if sem != nil { sem.Acquire() defer sem.Release() diff --git a/irc/config.go b/irc/config.go index 68019179..3c26577a 100644 --- a/irc/config.go +++ b/irc/config.go @@ -348,6 +348,11 @@ type AuthScriptConfig struct { Autocreate bool } +type IPCheckScriptConfig struct { + ScriptConfig `yaml:",inline"` + ExemptSASL bool `yaml:"exempt-sasl"` +} + // AccountRegistrationConfig controls account registration. type AccountRegistrationConfig struct { Enabled bool @@ -587,12 +592,12 @@ type Config struct { supportedCapsWithoutSTS *caps.Set capValues caps.Values Casemapping Casemapping - EnforceUtf8 bool `yaml:"enforce-utf8"` - OutputPath string `yaml:"output-path"` - IPCheckScript ScriptConfig `yaml:"ip-check-script"` - OverrideServicesHostname string `yaml:"override-services-hostname"` - MaxLineLen int `yaml:"max-line-len"` - SuppressLusers bool `yaml:"suppress-lusers"` + EnforceUtf8 bool `yaml:"enforce-utf8"` + OutputPath string `yaml:"output-path"` + IPCheckScript IPCheckScriptConfig `yaml:"ip-check-script"` + OverrideServicesHostname string `yaml:"override-services-hostname"` + MaxLineLen int `yaml:"max-line-len"` + SuppressLusers bool `yaml:"suppress-lusers"` } Roleplay struct { diff --git a/irc/server.go b/irc/server.go index 270a361c..58b8641c 100644 --- a/irc/server.go +++ b/irc/server.go @@ -200,7 +200,7 @@ func (server *Server) checkBans(config *Config, ipaddr net.IP, checkScripts bool server.logger.Warning("internal", "unexpected ban result", err.Error()) } - if checkScripts && config.Server.IPCheckScript.Enabled { + if checkScripts && config.Server.IPCheckScript.Enabled && !config.Server.IPCheckScript.ExemptSASL { output, err := CheckIPBan(server.semaphores.IPCheckScript, config.Server.IPCheckScript, ipaddr) if err != nil { server.logger.Error("internal", "couldn't check IP ban script", ipaddr.String(), err.Error()) @@ -267,9 +267,26 @@ func (server *Server) handleAlwaysOnExpirations() { } } -// -// server functionality -// +// handles server.ip-check-script.exempt-sasl: +// run the ip check script at the end of the handshake, only for anonymous connections +func (server *Server) checkBanScriptExemptSASL(config *Config, session *Session) (outcome AuthOutcome) { + // TODO add caching for this; see related code in (*server).checkBans; + // we should probably just put an LRU around this instead of using the DLINE system + ipaddr := session.IP() + output, err := CheckIPBan(server.semaphores.IPCheckScript, config.Server.IPCheckScript, ipaddr) + if err != nil { + server.logger.Error("internal", "couldn't check IP ban script", ipaddr.String(), err.Error()) + return authSuccess + } + if output.Result == IPBanned || output.Result == IPRequireSASL { + server.logger.Info("connect-ip", "Rejecting unauthenticated client due to ip-check-script", ipaddr.String()) + if output.BanMessage != "" { + session.client.requireSASLMessage = output.BanMessage + } + return authFailSaslRequired + } + return authSuccess +} func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) { // XXX PROXY or WEBIRC MUST be sent as the first line of the session; @@ -294,6 +311,10 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) { // before completing the other registration commands config := server.Config() authOutcome := c.isAuthorized(server, config, session, c.requireSASL) + if authOutcome == authSuccess && c.account == "" && + config.Server.IPCheckScript.Enabled && config.Server.IPCheckScript.ExemptSASL { + authOutcome = server.checkBanScriptExemptSASL(config, session) + } var quitMessage string switch authOutcome { case authFailPass: diff --git a/traditional.yaml b/traditional.yaml index ca3bbc88..d67da8ad 100644 --- a/traditional.yaml +++ b/traditional.yaml @@ -274,6 +274,9 @@ server: kill-timeout: 1s # how many scripts are allowed to run at once? 0 for no limit: max-concurrency: 64 + # if true, only check anonymous connections (not logged into an account) + # at the very end of the handshake: + exempt-sasl: false # IP cloaking hides users' IP addresses from other users and from channel admins # (but not from server admins), while still allowing channel admins to ban