3
0
mirror of https://github.com/ergochat/ergo.git synced 2025-11-13 20:47:24 +01:00

changes to OPER command

* Impose a throttle on OPER attempts regardless of whether they caused a
  password check.
* Never disconnect the client on a failed attempt, even if there was a
  password check.
* Change error numeric to ERR_NOOPERHOST
* Explicit information about the failure in the server log (copying Insp)

Fixes #2296.
This commit is contained in:
Shivaram Lingamneni 2025-11-09 19:32:25 -05:00
parent efc1627d23
commit 6fdac13ad4
6 changed files with 43 additions and 8 deletions

View File

@ -358,6 +358,10 @@ server:
secure-nets: secure-nets:
# - "10.0.0.0/8" # - "10.0.0.0/8"
# allow attempts to OPER with a password at most this often. default to
# 10 seconds when unset.
oper-throttle: 10s
# Ergo will write files to disk under certain circumstances, e.g., # Ergo will write files to disk under certain circumstances, e.g.,
# CPU profiling or data export. by default, these files will be written # CPU profiling or data export. by default, these files will be written
# to the working directory. set this to customize: # to the working directory. set this to customize:

View File

@ -527,7 +527,7 @@ If your client or bot is failing to connect to Ergo, here are some things to che
## Why can't I oper? ## Why can't I oper?
If you try to oper unsuccessfully, Ergo will disconnect you from the network. If you're unable to oper, here are some things to double-check: If your `OPER` command fails, check your server logs for more information. Here are some general issues to double-check:
1. Did you correctly generate the hashed password with `ergo genpasswd`? 1. Did you correctly generate the hashed password with `ergo genpasswd`?
1. Did you add the password hash to the correct config file, then save the file? 1. Did you add the password hash to the correct config file, then save the file?

View File

@ -189,6 +189,8 @@ type Session struct {
fakelag Fakelag fakelag Fakelag
deferredFakelagCount int deferredFakelagCount int
lastOperAttempt time.Time
certfp string certfp string
peerCerts []*x509.Certificate peerCerts []*x509.Certificate
sasl saslStatus sasl saslStatus

View File

@ -599,6 +599,7 @@ type Config struct {
Cloaks cloaks.CloakConfig `yaml:"ip-cloaking"` Cloaks cloaks.CloakConfig `yaml:"ip-cloaking"`
SecureNetDefs []string `yaml:"secure-nets"` SecureNetDefs []string `yaml:"secure-nets"`
secureNets []net.IPNet secureNets []net.IPNet
OperThrottle time.Duration `yaml:"oper-throttle"`
supportedCaps *caps.Set supportedCaps *caps.Set
supportedCapsWithoutSTS *caps.Set supportedCapsWithoutSTS *caps.Set
capValues caps.Values capValues caps.Values
@ -1480,6 +1481,10 @@ func LoadConfig(filename string) (config *Config, err error) {
config.Server.supportedCaps.Disable(caps.SASL) config.Server.supportedCaps.Disable(caps.SASL)
} }
if config.Server.OperThrottle <= 0 {
config.Server.OperThrottle = 10 * time.Second
}
if err := config.Accounts.OAuth2.Postprocess(); err != nil { if err := config.Accounts.OAuth2.Postprocess(); err != nil {
return nil, err return nil, err
} }

View File

@ -2545,8 +2545,19 @@ func operHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respons
return false return false
} }
config := server.Config()
now := time.Now()
nextAllowableAttempt := rb.session.lastOperAttempt.Add(config.Server.OperThrottle)
if now.Before(nextAllowableAttempt) {
timeLeft := nextAllowableAttempt.Sub(now).Round(time.Millisecond)
rb.Add(nil, server.name, ERR_NOOPERHOST, client.Nick(), fmt.Sprintf(client.t("You must wait %v before issuing OPER again"), timeLeft))
return false
}
rb.session.lastOperAttempt = now
// must pass at least one check, and all enabled checks // must pass at least one check, and all enabled checks
var checkPassed, checkFailed, passwordFailed bool var checkPassed, checkFailed, certFailed, passwordFailed bool
oper := server.GetOperator(msg.Params[0]) oper := server.GetOperator(msg.Params[0])
if oper != nil { if oper != nil {
if oper.Certfp != "" { if oper.Certfp != "" {
@ -2554,11 +2565,13 @@ func operHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respons
checkPassed = true checkPassed = true
} else { } else {
checkFailed = true checkFailed = true
certFailed = true
} }
} }
if !checkFailed && oper.Pass != nil { if !checkFailed && oper.Pass != nil {
if len(msg.Params) == 1 { if len(msg.Params) == 1 {
checkFailed = true checkFailed = true
passwordFailed = true
} else if bcrypt.CompareHashAndPassword(oper.Pass, []byte(msg.Params[1])) != nil { } else if bcrypt.CompareHashAndPassword(oper.Pass, []byte(msg.Params[1])) != nil {
checkFailed = true checkFailed = true
passwordFailed = true passwordFailed = true
@ -2569,14 +2582,21 @@ func operHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respons
} }
if !checkPassed || checkFailed { if !checkPassed || checkFailed {
rb.Add(nil, server.name, ERR_PASSWDMISMATCH, client.Nick(), client.t("Password incorrect")) rb.Add(nil, server.name, ERR_NOOPERHOST, client.Nick(), client.t("OPER failed; check the server logs for details."))
// #951: only disconnect them if we actually tried to check a password for them
if passwordFailed { // hopefully not too spammy given the throttling:
client.Quit(client.t("Password incorrect"), rb.session) if oper == nil {
return true server.logger.Info("opers", "OPER failed with invalid oper name", msg.Params[0])
} else if certFailed {
server.logger.Info("opers", "OPER attempt for", msg.Params[0], "failed with invalid certfp")
} else if passwordFailed {
server.logger.Info("opers", "OPER attempt for", msg.Params[0], "failed with invalid password")
} else { } else {
return false // should not be possible given config validation
server.logger.Info("opers", "OPER attempt for", msg.Params[0], "failed with invalid config")
} }
return false
} }
if oper != nil { if oper != nil {

View File

@ -330,6 +330,10 @@ server:
secure-nets: secure-nets:
# - "10.0.0.0/8" # - "10.0.0.0/8"
# allow attempts to OPER with a password at most this often. default to
# 10 seconds when unset.
oper-throttle: 10s
# Ergo will write files to disk under certain circumstances, e.g., # Ergo will write files to disk under certain circumstances, e.g.,
# CPU profiling or data export. by default, these files will be written # CPU profiling or data export. by default, these files will be written
# to the working directory. set this to customize: # to the working directory. set this to customize: