3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-14 07:59:31 +01:00

Merge pull request #916 from slingamn/issue913_accountthrottle.1

fix #913
This commit is contained in:
Shivaram Lingamneni 2020-03-29 18:00:23 -07:00 committed by GitHub
commit 7bdf4441cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 87 additions and 29 deletions

View File

@ -15,6 +15,7 @@ import (
"unicode" "unicode"
"github.com/oragono/oragono/irc/caps" "github.com/oragono/oragono/irc/caps"
"github.com/oragono/oragono/irc/connection_limits"
"github.com/oragono/oragono/irc/ldap" "github.com/oragono/oragono/irc/ldap"
"github.com/oragono/oragono/irc/passwd" "github.com/oragono/oragono/irc/passwd"
"github.com/oragono/oragono/irc/utils" "github.com/oragono/oragono/irc/utils"
@ -62,6 +63,7 @@ type AccountManager struct {
nickToAccount map[string]string nickToAccount map[string]string
skeletonToAccount map[string]string skeletonToAccount map[string]string
accountToMethod map[string]NickEnforcementMethod accountToMethod map[string]NickEnforcementMethod
registerThrottle connection_limits.GenericThrottle
} }
func (am *AccountManager) Initialize(server *Server) { func (am *AccountManager) Initialize(server *Server) {
@ -75,6 +77,24 @@ func (am *AccountManager) Initialize(server *Server) {
am.buildNickToAccountIndex(config) am.buildNickToAccountIndex(config)
am.initVHostRequestQueue(config) am.initVHostRequestQueue(config)
am.createAlwaysOnClients(config) am.createAlwaysOnClients(config)
am.resetRegisterThrottle(config)
}
func (am *AccountManager) resetRegisterThrottle(config *Config) {
am.Lock()
defer am.Unlock()
am.registerThrottle = connection_limits.GenericThrottle{
Duration: config.Accounts.Registration.Throttling.Duration,
Limit: config.Accounts.Registration.Throttling.MaxAttempts,
}
}
func (am *AccountManager) touchRegisterThrottle() (throttled bool) {
am.Lock()
defer am.Unlock()
throttled, _ = am.registerThrottle.Touch()
return
} }
func (am *AccountManager) createAlwaysOnClients(config *Config) { func (am *AccountManager) createAlwaysOnClients(config *Config) {
@ -363,6 +383,15 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames
return errFeatureDisabled return errFeatureDisabled
} }
if client != nil && client.Account() != "" {
return errAccountAlreadyLoggedIn
}
if client != nil && am.touchRegisterThrottle() {
am.server.logger.Warning("accounts", "global registration throttle exceeded by client", client.Nick())
return errLimitExceeded
}
// if nick reservation is enabled, you can only register your current nickname // if nick reservation is enabled, you can only register your current nickname
// as an account; this prevents "land-grab" situations where someone else // as an account; this prevents "land-grab" situations where someone else
// registers your nick out from under you and then NS GHOSTs you // registers your nick out from under you and then NS GHOSTs you
@ -725,6 +754,10 @@ func (am *AccountManager) Verify(client *Client, account string, code string) er
return errAccountVerificationFailed return errAccountVerificationFailed
} }
if client != nil && client.Account() != "" {
return errAccountAlreadyLoggedIn
}
verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount) verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount) accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount) accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)

View File

@ -229,6 +229,29 @@ type MulticlientConfig struct {
AlwaysOn PersistentStatus `yaml:"always-on"` AlwaysOn PersistentStatus `yaml:"always-on"`
} }
type throttleConfig struct {
Enabled bool
Duration time.Duration
MaxAttempts int `yaml:"max-attempts"`
}
type ThrottleConfig struct {
throttleConfig
}
func (t *ThrottleConfig) UnmarshalYAML(unmarshal func(interface{}) error) (err error) {
// note that this technique only works if the zero value of the struct
// doesn't need any postprocessing (because if the field is omitted entirely
// from the YAML, then UnmarshalYAML won't be called at all)
if err = unmarshal(&t.throttleConfig); err != nil {
return
}
if !t.Enabled {
t.MaxAttempts = 0 // limit of 0 means disabled
}
return
}
type AccountConfig struct { type AccountConfig struct {
Registration AccountRegistrationConfig Registration AccountRegistrationConfig
AuthenticationEnabled bool `yaml:"authentication-enabled"` AuthenticationEnabled bool `yaml:"authentication-enabled"`
@ -238,11 +261,7 @@ type AccountConfig struct {
exemptedNets []net.IPNet exemptedNets []net.IPNet
} `yaml:"require-sasl"` } `yaml:"require-sasl"`
LDAP ldap.ServerConfig LDAP ldap.ServerConfig
LoginThrottling struct { LoginThrottling ThrottleConfig `yaml:"login-throttling"`
Enabled bool
Duration time.Duration
MaxAttempts int `yaml:"max-attempts"`
} `yaml:"login-throttling"`
SkipServerPassword bool `yaml:"skip-server-password"` SkipServerPassword bool `yaml:"skip-server-password"`
NickReservation struct { NickReservation struct {
Enabled bool Enabled bool
@ -266,6 +285,7 @@ type AccountConfig struct {
// AccountRegistrationConfig controls account registration. // AccountRegistrationConfig controls account registration.
type AccountRegistrationConfig struct { type AccountRegistrationConfig struct {
Enabled bool Enabled bool
Throttling ThrottleConfig
EnabledCallbacks []string `yaml:"enabled-callbacks"` EnabledCallbacks []string `yaml:"enabled-callbacks"`
EnabledCredentialTypes []string `yaml:"-"` EnabledCredentialTypes []string `yaml:"-"`
VerifyTimeout custime.Duration `yaml:"verify-timeout"` VerifyTimeout custime.Duration `yaml:"verify-timeout"`
@ -997,10 +1017,6 @@ func LoadConfig(filename string) (config *Config, err error) {
} }
} }
if !config.Accounts.LoginThrottling.Enabled {
config.Accounts.LoginThrottling.MaxAttempts = 0 // limit of 0 means disabled
}
config.Server.capValues[caps.SASL] = "PLAIN,EXTERNAL" config.Server.capValues[caps.SASL] = "PLAIN,EXTERNAL"
if !config.Accounts.AuthenticationEnabled { if !config.Accounts.AuthenticationEnabled {
config.Server.supportedCaps.Disable(caps.SASL) config.Server.supportedCaps.Disable(caps.SASL)

View File

@ -22,6 +22,7 @@ var (
errAccountBadPassphrase = errors.New(`Passphrase contains forbidden characters or is otherwise invalid`) errAccountBadPassphrase = errors.New(`Passphrase contains forbidden characters or is otherwise invalid`)
errAccountNickReservationFailed = errors.New("Could not (un)reserve nick") errAccountNickReservationFailed = errors.New("Could not (un)reserve nick")
errAccountNotLoggedIn = errors.New("You're not logged into an account") errAccountNotLoggedIn = errors.New("You're not logged into an account")
errAccountAlreadyLoggedIn = errors.New("You're already logged into an account")
errAccountTooManyNicks = errors.New("Account has too many reserved nicks") errAccountTooManyNicks = errors.New("Account has too many reserved nicks")
errAccountUnverified = errors.New(`Account is not yet verified`) errAccountUnverified = errors.New(`Account is not yet verified`)
errAccountVerificationFailed = errors.New("Account verification failed") errAccountVerificationFailed = errors.New("Account verification failed")

View File

@ -66,10 +66,10 @@ func registrationErrorToMessageAndCode(err error) (message, code string) {
case errAccountBadPassphrase: case errAccountBadPassphrase:
code = "REG_INVALID_CREDENTIAL" code = "REG_INVALID_CREDENTIAL"
message = err.Error() message = err.Error()
case errAccountAlreadyRegistered, errAccountAlreadyVerified, errAccountAlreadyUnregistered: case errAccountAlreadyRegistered, errAccountAlreadyVerified, errAccountAlreadyUnregistered, errAccountAlreadyLoggedIn, errAccountCreation, errAccountMustHoldNick, errAccountBadPassphrase, errCertfpAlreadyExists, errFeatureDisabled:
message = err.Error()
case errAccountCreation, errAccountMustHoldNick, errAccountBadPassphrase, errCertfpAlreadyExists, errFeatureDisabled:
message = err.Error() message = err.Error()
case errLimitExceeded:
message = `There have been too many registration attempts recently; try again later`
} }
return return
} }

View File

@ -616,7 +616,9 @@ func nsGroupHandler(server *Server, client *Client, command string, params []str
} }
func nsLoginThrottleCheck(client *Client, rb *ResponseBuffer) (success bool) { func nsLoginThrottleCheck(client *Client, rb *ResponseBuffer) (success bool) {
client.stateMutex.Lock()
throttled, remainingTime := client.loginThrottle.Touch() throttled, remainingTime := client.loginThrottle.Touch()
client.stateMutex.Unlock()
if throttled { if throttled {
nsNotice(rb, fmt.Sprintf(client.t("Please wait at least %v and try again"), remainingTime)) nsNotice(rb, fmt.Sprintf(client.t("Please wait at least %v and try again"), remainingTime))
return false return false
@ -741,11 +743,6 @@ func nsRegisterHandler(server *Server, client *Client, command string, params []
} }
} }
if details.account != "" {
nsNotice(rb, client.t("You're already logged into an account"))
return
}
if !nsLoginThrottleCheck(client, rb) { if !nsLoginThrottleCheck(client, rb) {
return return
} }
@ -793,13 +790,10 @@ func nsRegisterHandler(server *Server, client *Client, command string, params []
message := fmt.Sprintf(messageTemplate, fmt.Sprintf("%s:%s", callbackNamespace, callbackValue)) message := fmt.Sprintf(messageTemplate, fmt.Sprintf("%s:%s", callbackNamespace, callbackValue))
nsNotice(rb, message) nsNotice(rb, message)
} }
} } else {
// details could not be stored and relevant numerics have been dispatched, abort // details could not be stored and relevant numerics have been dispatched, abort
message, _ := registrationErrorToMessageAndCode(err) message, _ := registrationErrorToMessageAndCode(err)
if err != nil {
nsNotice(rb, client.t(message)) nsNotice(rb, client.t(message))
return
} }
} }
@ -894,11 +888,14 @@ func nsVerifyHandler(server *Server, client *Client, command string, params []st
err := server.accounts.Verify(client, username, code) err := server.accounts.Verify(client, username, code)
var errorMessage string var errorMessage string
if err == errAccountVerificationInvalidCode || err == errAccountAlreadyVerified { if err != nil {
switch err {
case errAccountAlreadyLoggedIn, errAccountVerificationInvalidCode, errAccountAlreadyVerified:
errorMessage = err.Error() errorMessage = err.Error()
} else if err != nil { default:
errorMessage = errAccountVerificationFailed.Error() errorMessage = errAccountVerificationFailed.Error()
} }
}
if errorMessage != "" { if errorMessage != "" {
nsNotice(rb, client.t(errorMessage)) nsNotice(rb, client.t(errorMessage))

View File

@ -634,6 +634,9 @@ func (server *Server) applyConfig(config *Config) (err error) {
client.resizeHistory(config) client.resizeHistory(config)
} }
} }
if oldConfig.Accounts.Registration.Throttling != config.Accounts.Registration.Throttling {
server.accounts.resetRegisterThrottle(config)
}
} }
// activate the new config // activate the new config

View File

@ -266,6 +266,14 @@ accounts:
# the `accreg` capability can still create accounts with `/NICKSERV SAREGISTER` # the `accreg` capability can still create accounts with `/NICKSERV SAREGISTER`
enabled: true enabled: true
# global throttle on new account creation
throttling:
enabled: true
# window
duration: 10m
# number of attempts allowed within the window
max-attempts: 30
# this is the bcrypt cost we'll use for account passwords # this is the bcrypt cost we'll use for account passwords
bcrypt-cost: 9 bcrypt-cost: 9