mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-25 21:39:25 +01:00
add support for login throttling
This commit is contained in:
parent
3cd3601a30
commit
f94f737b31
1
Makefile
1
Makefile
@ -20,6 +20,7 @@ test:
|
|||||||
python3 ./gencapdefs.py | diff - ${capdef_file}
|
python3 ./gencapdefs.py | diff - ${capdef_file}
|
||||||
cd irc && go test . && go vet .
|
cd irc && go test . && go vet .
|
||||||
cd irc/caps && go test . && go vet .
|
cd irc/caps && go test . && go vet .
|
||||||
|
cd irc/connection_limits && go test . && go vet .
|
||||||
cd irc/history && go test . && go vet .
|
cd irc/history && go test . && go vet .
|
||||||
cd irc/isupport && go test . && go vet .
|
cd irc/isupport && go test . && go vet .
|
||||||
cd irc/modes && go test . && go vet .
|
cd irc/modes && go test . && go vet .
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/goshuirc/irc-go/ircmsg"
|
"github.com/goshuirc/irc-go/ircmsg"
|
||||||
ident "github.com/oragono/go-ident"
|
ident "github.com/oragono/go-ident"
|
||||||
"github.com/oragono/oragono/irc/caps"
|
"github.com/oragono/oragono/irc/caps"
|
||||||
|
"github.com/oragono/oragono/irc/connection_limits"
|
||||||
"github.com/oragono/oragono/irc/history"
|
"github.com/oragono/oragono/irc/history"
|
||||||
"github.com/oragono/oragono/irc/modes"
|
"github.com/oragono/oragono/irc/modes"
|
||||||
"github.com/oragono/oragono/irc/sno"
|
"github.com/oragono/oragono/irc/sno"
|
||||||
@ -73,6 +74,7 @@ type Client struct {
|
|||||||
isDestroyed bool
|
isDestroyed bool
|
||||||
isQuitting bool
|
isQuitting bool
|
||||||
languages []string
|
languages []string
|
||||||
|
loginThrottle connection_limits.GenericThrottle
|
||||||
maxlenTags uint32
|
maxlenTags uint32
|
||||||
maxlenRest uint32
|
maxlenRest uint32
|
||||||
nick string
|
nick string
|
||||||
@ -126,14 +128,18 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) {
|
|||||||
fullLineLenLimit := config.Limits.LineLen.Tags + config.Limits.LineLen.Rest
|
fullLineLenLimit := config.Limits.LineLen.Tags + config.Limits.LineLen.Rest
|
||||||
socket := NewSocket(conn, fullLineLenLimit*2, config.Server.MaxSendQBytes)
|
socket := NewSocket(conn, fullLineLenLimit*2, config.Server.MaxSendQBytes)
|
||||||
client := &Client{
|
client := &Client{
|
||||||
atime: now,
|
atime: now,
|
||||||
authorized: server.Password() == nil,
|
authorized: server.Password() == nil,
|
||||||
capabilities: caps.NewSet(),
|
capabilities: caps.NewSet(),
|
||||||
capState: caps.NoneState,
|
capState: caps.NoneState,
|
||||||
capVersion: caps.Cap301,
|
capVersion: caps.Cap301,
|
||||||
channels: make(ChannelSet),
|
channels: make(ChannelSet),
|
||||||
ctime: now,
|
ctime: now,
|
||||||
flags: modes.NewModeSet(),
|
flags: modes.NewModeSet(),
|
||||||
|
loginThrottle: connection_limits.GenericThrottle{
|
||||||
|
Duration: config.Accounts.LoginThrottling.Duration,
|
||||||
|
Limit: config.Accounts.LoginThrottling.MaxAttempts,
|
||||||
|
},
|
||||||
server: server,
|
server: server,
|
||||||
socket: socket,
|
socket: socket,
|
||||||
accountName: "*",
|
accountName: "*",
|
||||||
|
@ -54,10 +54,15 @@ func (conf *TLSListenConfig) Config() (*tls.Config, error) {
|
|||||||
|
|
||||||
type AccountConfig struct {
|
type AccountConfig struct {
|
||||||
Registration AccountRegistrationConfig
|
Registration AccountRegistrationConfig
|
||||||
AuthenticationEnabled bool `yaml:"authentication-enabled"`
|
AuthenticationEnabled bool `yaml:"authentication-enabled"`
|
||||||
SkipServerPassword bool `yaml:"skip-server-password"`
|
LoginThrottling struct {
|
||||||
NickReservation NickReservationConfig `yaml:"nick-reservation"`
|
Enabled bool
|
||||||
VHosts VHostConfig
|
Duration time.Duration
|
||||||
|
MaxAttempts int `yaml:"max-attempts"`
|
||||||
|
} `yaml:"login-throttling"`
|
||||||
|
SkipServerPassword bool `yaml:"skip-server-password"`
|
||||||
|
NickReservation NickReservationConfig `yaml:"nick-reservation"`
|
||||||
|
VHosts VHostConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountRegistrationConfig controls account registration.
|
// AccountRegistrationConfig controls account registration.
|
||||||
@ -558,6 +563,10 @@ func LoadConfig(filename string) (config *Config, err error) {
|
|||||||
config.Accounts.VHosts.ValidRegexp = defaultValidVhostRegex
|
config.Accounts.VHosts.ValidRegexp = defaultValidVhostRegex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !config.Accounts.LoginThrottling.Enabled {
|
||||||
|
config.Accounts.LoginThrottling.MaxAttempts = 0 // limit of 0 means disabled
|
||||||
|
}
|
||||||
|
|
||||||
maxSendQBytes, err := bytefmt.ToBytes(config.Server.MaxSendQString)
|
maxSendQBytes, err := bytefmt.ToBytes(config.Server.MaxSendQString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Could not parse maximum SendQ size (make sure it only contains whole numbers): %s", err.Error())
|
return nil, fmt.Errorf("Could not parse maximum SendQ size (make sure it only contains whole numbers): %s", err.Error())
|
||||||
|
@ -26,8 +26,45 @@ type ThrottlerConfig struct {
|
|||||||
|
|
||||||
// ThrottleDetails holds the connection-throttling details for a subnet/IP.
|
// ThrottleDetails holds the connection-throttling details for a subnet/IP.
|
||||||
type ThrottleDetails struct {
|
type ThrottleDetails struct {
|
||||||
Start time.Time
|
Start time.Time
|
||||||
ClientCount int
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenericThrottle allows enforcing limits of the form
|
||||||
|
// "at most X events per time window of duration Y"
|
||||||
|
type GenericThrottle struct {
|
||||||
|
ThrottleDetails // variable state: what events have been seen
|
||||||
|
// these are constant after creation:
|
||||||
|
Duration time.Duration // window length to consider
|
||||||
|
Limit int // number of events allowed per window
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touch checks whether an additional event is allowed:
|
||||||
|
// it either denies it (by returning false) or allows it (by returning true)
|
||||||
|
// and records it
|
||||||
|
func (g *GenericThrottle) Touch() (throttled bool, remainingTime time.Duration) {
|
||||||
|
return g.touch(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GenericThrottle) touch(now time.Time) (throttled bool, remainingTime time.Duration) {
|
||||||
|
if g.Limit == 0 {
|
||||||
|
return // limit of 0 disables throttling
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed := now.Sub(g.Start)
|
||||||
|
if elapsed > g.Duration {
|
||||||
|
// reset window, record the operation
|
||||||
|
g.Start = now
|
||||||
|
g.Count = 1
|
||||||
|
return false, 0
|
||||||
|
} else if g.Count >= g.Limit {
|
||||||
|
// we are throttled
|
||||||
|
return true, g.Start.Add(g.Duration).Sub(now)
|
||||||
|
} else {
|
||||||
|
// we are not throttled, record the operation
|
||||||
|
g.Count += 1
|
||||||
|
return false, 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throttler manages automated client connection throttling.
|
// Throttler manages automated client connection throttling.
|
||||||
@ -102,21 +139,21 @@ func (ct *Throttler) AddClient(addr net.IP) error {
|
|||||||
ct.maskAddr(addr)
|
ct.maskAddr(addr)
|
||||||
addrString := addr.String()
|
addrString := addr.String()
|
||||||
|
|
||||||
details, exists := ct.population[addrString]
|
details := ct.population[addrString] // retrieve mutable throttle state from the map
|
||||||
if !exists || details.Start.Add(ct.duration).Before(time.Now()) {
|
// add in constant state to process the limiting operation
|
||||||
details = ThrottleDetails{
|
g := GenericThrottle{
|
||||||
Start: time.Now(),
|
ThrottleDetails: details,
|
||||||
}
|
Duration: ct.duration,
|
||||||
|
Limit: ct.subnetLimit,
|
||||||
}
|
}
|
||||||
|
throttled, _ := g.Touch() // actually check the limit
|
||||||
|
ct.population[addrString] = g.ThrottleDetails // store modified mutable state
|
||||||
|
|
||||||
if details.ClientCount+1 > ct.subnetLimit {
|
if throttled {
|
||||||
return errTooManyClients
|
return errTooManyClients
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
details.ClientCount++
|
|
||||||
ct.population[addrString] = details
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ct *Throttler) BanDuration() time.Duration {
|
func (ct *Throttler) BanDuration() time.Duration {
|
||||||
|
86
irc/connection_limits/throttler_test.go
Normal file
86
irc/connection_limits/throttler_test.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// Copyright (c) 2018 Shivaram Lingamneni
|
||||||
|
// released under the MIT license
|
||||||
|
|
||||||
|
package connection_limits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func assertEqual(supplied, expected interface{}, t *testing.T) {
|
||||||
|
if !reflect.DeepEqual(supplied, expected) {
|
||||||
|
t.Errorf("expected %v but got %v", expected, supplied)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericThrottle(t *testing.T) {
|
||||||
|
minute, _ := time.ParseDuration("1m")
|
||||||
|
second, _ := time.ParseDuration("1s")
|
||||||
|
zero, _ := time.ParseDuration("0s")
|
||||||
|
|
||||||
|
throttler := GenericThrottle{
|
||||||
|
Duration: minute,
|
||||||
|
Limit: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
throttled, remaining := throttler.touch(now)
|
||||||
|
assertEqual(throttled, false, t)
|
||||||
|
assertEqual(remaining, zero, t)
|
||||||
|
|
||||||
|
now = now.Add(second)
|
||||||
|
throttled, remaining = throttler.touch(now)
|
||||||
|
assertEqual(throttled, false, t)
|
||||||
|
assertEqual(remaining, zero, t)
|
||||||
|
|
||||||
|
now = now.Add(second)
|
||||||
|
throttled, remaining = throttler.touch(now)
|
||||||
|
assertEqual(throttled, true, t)
|
||||||
|
assertEqual(remaining, 58*second, t)
|
||||||
|
|
||||||
|
now = now.Add(minute)
|
||||||
|
throttled, remaining = throttler.touch(now)
|
||||||
|
assertEqual(throttled, false, t)
|
||||||
|
assertEqual(remaining, zero, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericThrottleDisabled(t *testing.T) {
|
||||||
|
minute, _ := time.ParseDuration("1m")
|
||||||
|
throttler := GenericThrottle{
|
||||||
|
Duration: minute,
|
||||||
|
Limit: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 1024; i += 1 {
|
||||||
|
throttled, _ := throttler.Touch()
|
||||||
|
if throttled {
|
||||||
|
t.Error("disabled throttler should not throttle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectionThrottle(t *testing.T) {
|
||||||
|
minute, _ := time.ParseDuration("1m")
|
||||||
|
maxConnections := 3
|
||||||
|
config := ThrottlerConfig{
|
||||||
|
Enabled: true,
|
||||||
|
CidrLenIPv4: 32,
|
||||||
|
CidrLenIPv6: 64,
|
||||||
|
ConnectionsPerCidr: maxConnections,
|
||||||
|
Duration: minute,
|
||||||
|
}
|
||||||
|
throttler := NewThrottler()
|
||||||
|
throttler.ApplyConfig(config)
|
||||||
|
|
||||||
|
addr := net.ParseIP("8.8.8.8")
|
||||||
|
|
||||||
|
for i := 0; i < maxConnections; i += 1 {
|
||||||
|
err := throttler.AddClient(addr)
|
||||||
|
assertEqual(err, nil, t)
|
||||||
|
}
|
||||||
|
err := throttler.AddClient(addr)
|
||||||
|
assertEqual(err, errTooManyClients, t)
|
||||||
|
}
|
@ -83,9 +83,10 @@ func parseCallback(spec string, config *AccountConfig) (callbackNamespace string
|
|||||||
|
|
||||||
// ACC REGISTER <accountname> [callback_namespace:]<callback> [cred_type] :<credential>
|
// ACC REGISTER <accountname> [callback_namespace:]<callback> [cred_type] :<credential>
|
||||||
func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
|
nick := client.Nick()
|
||||||
// clients can't reg new accounts if they're already logged in
|
// clients can't reg new accounts if they're already logged in
|
||||||
if client.LoggedIntoAccount() {
|
if client.LoggedIntoAccount() {
|
||||||
rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, client.nick, "*", client.t("You're already logged into an account"))
|
rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, nick, "*", client.t("You're already logged into an account"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,12 +95,12 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r
|
|||||||
casefoldedAccount, err := CasefoldName(account)
|
casefoldedAccount, err := CasefoldName(account)
|
||||||
// probably don't need explicit check for "*" here... but let's do it anyway just to make sure
|
// probably don't need explicit check for "*" here... but let's do it anyway just to make sure
|
||||||
if err != nil || msg.Params[1] == "*" {
|
if err != nil || msg.Params[1] == "*" {
|
||||||
rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, client.nick, account, client.t("Account name is not valid"))
|
rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, nick, account, client.t("Account name is not valid"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(msg.Params) < 4 {
|
if len(msg.Params) < 4 {
|
||||||
rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, client.t("Not enough parameters"))
|
rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, nick, msg.Command, client.t("Not enough parameters"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +108,7 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r
|
|||||||
callbackNamespace, callbackValue := parseCallback(callbackSpec, server.AccountConfig())
|
callbackNamespace, callbackValue := parseCallback(callbackSpec, server.AccountConfig())
|
||||||
|
|
||||||
if callbackNamespace == "" {
|
if callbackNamespace == "" {
|
||||||
rb.Add(nil, server.name, ERR_REG_INVALID_CALLBACK, client.nick, account, callbackSpec, client.t("Callback namespace is not supported"))
|
rb.Add(nil, server.name, ERR_REG_INVALID_CALLBACK, nick, account, callbackSpec, client.t("Callback namespace is not supported"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,12 +132,12 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if credentialType == "certfp" && client.certfp == "" {
|
if credentialType == "certfp" && client.certfp == "" {
|
||||||
rb.Add(nil, server.name, ERR_REG_INVALID_CRED_TYPE, client.nick, credentialType, callbackNamespace, client.t("You are not using a TLS certificate"))
|
rb.Add(nil, server.name, ERR_REG_INVALID_CRED_TYPE, nick, credentialType, callbackNamespace, client.t("You are not using a TLS certificate"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !credentialValid {
|
if !credentialValid {
|
||||||
rb.Add(nil, server.name, ERR_REG_INVALID_CRED_TYPE, client.nick, credentialType, callbackNamespace, client.t("Credential type is not supported"))
|
rb.Add(nil, server.name, ERR_REG_INVALID_CRED_TYPE, nick, credentialType, callbackNamespace, client.t("Credential type is not supported"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,6 +147,13 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r
|
|||||||
} else if credentialType == "passphrase" {
|
} else if credentialType == "passphrase" {
|
||||||
passphrase = credentialValue
|
passphrase = credentialValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throttled, remainingTime := client.loginThrottle.Touch()
|
||||||
|
if throttled {
|
||||||
|
rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, nick, fmt.Sprintf(client.t("Please wait at least %v and try again"), remainingTime))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
err = server.accounts.Register(client, account, callbackNamespace, callbackValue, passphrase, certfp)
|
err = server.accounts.Register(client, account, callbackNamespace, callbackValue, passphrase, certfp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := "Unknown"
|
msg := "Unknown"
|
||||||
@ -161,7 +169,7 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r
|
|||||||
if err == errAccountAlreadyRegistered || err == errAccountCreation || err == errCertfpAlreadyExists {
|
if err == errAccountAlreadyRegistered || err == errAccountCreation || err == errCertfpAlreadyExists {
|
||||||
msg = err.Error()
|
msg = err.Error()
|
||||||
}
|
}
|
||||||
rb.Add(nil, server.name, code, client.nick, "ACC", "REGISTER", client.t(msg))
|
rb.Add(nil, server.name, code, nick, "ACC", "REGISTER", client.t(msg))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,7 +183,7 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r
|
|||||||
} else {
|
} else {
|
||||||
messageTemplate := client.t("Account created, pending verification; verification code has been sent to %s:%s")
|
messageTemplate := client.t("Account created, pending verification; verification code has been sent to %s:%s")
|
||||||
message := fmt.Sprintf(messageTemplate, callbackNamespace, callbackValue)
|
message := fmt.Sprintf(messageTemplate, callbackNamespace, callbackValue)
|
||||||
rb.Add(nil, server.name, RPL_REG_VERIFICATION_REQUIRED, client.nick, casefoldedAccount, message)
|
rb.Add(nil, server.name, RPL_REG_VERIFICATION_REQUIRED, nick, casefoldedAccount, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@ -336,6 +344,8 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value []
|
|||||||
|
|
||||||
var accountKey, authzid string
|
var accountKey, authzid string
|
||||||
|
|
||||||
|
nick := client.Nick()
|
||||||
|
|
||||||
if len(splitValue) == 3 {
|
if len(splitValue) == 3 {
|
||||||
accountKey = string(splitValue[0])
|
accountKey = string(splitValue[0])
|
||||||
authzid = string(splitValue[1])
|
authzid = string(splitValue[1])
|
||||||
@ -343,11 +353,17 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value []
|
|||||||
if accountKey == "" {
|
if accountKey == "" {
|
||||||
accountKey = authzid
|
accountKey = authzid
|
||||||
} else if accountKey != authzid {
|
} else if accountKey != authzid {
|
||||||
rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed: authcid and authzid should be the same"))
|
rb.Add(nil, server.name, ERR_SASLFAIL, nick, client.t("SASL authentication failed: authcid and authzid should be the same"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed: Invalid auth blob"))
|
rb.Add(nil, server.name, ERR_SASLFAIL, nick, client.t("SASL authentication failed: Invalid auth blob"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
throttled, remainingTime := client.loginThrottle.Touch()
|
||||||
|
if throttled {
|
||||||
|
rb.Add(nil, server.name, ERR_SASLFAIL, nick, fmt.Sprintf(client.t("Please wait at least %v and try again"), remainingTime))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,7 +371,7 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value []
|
|||||||
err := server.accounts.AuthenticateByPassphrase(client, accountKey, password)
|
err := server.accounts.AuthenticateByPassphrase(client, accountKey, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := authErrorToMessage(server, err)
|
msg := authErrorToMessage(server, err)
|
||||||
rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, fmt.Sprintf("%s: %s", client.t("SASL authentication failed"), client.t(msg)))
|
rb.Add(nil, server.name, ERR_SASLFAIL, nick, fmt.Sprintf("%s: %s", client.t("SASL authentication failed"), client.t(msg)))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,6 +200,15 @@ func nsGroupHandler(server *Server, client *Client, command, params string, rb *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nsLoginThrottleCheck(client *Client, rb *ResponseBuffer) (success bool) {
|
||||||
|
throttled, remainingTime := client.loginThrottle.Touch()
|
||||||
|
if throttled {
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("Please wait at least %v and try again"), remainingTime))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func nsIdentifyHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
|
func nsIdentifyHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
|
||||||
loginSuccessful := false
|
loginSuccessful := false
|
||||||
|
|
||||||
@ -207,6 +216,9 @@ func nsIdentifyHandler(server *Server, client *Client, command, params string, r
|
|||||||
|
|
||||||
// try passphrase
|
// try passphrase
|
||||||
if username != "" && passphrase != "" {
|
if username != "" && passphrase != "" {
|
||||||
|
if !nsLoginThrottleCheck(client, rb) {
|
||||||
|
return
|
||||||
|
}
|
||||||
err := server.accounts.AuthenticateByPassphrase(client, username, passphrase)
|
err := server.accounts.AuthenticateByPassphrase(client, username, passphrase)
|
||||||
loginSuccessful = (err == nil)
|
loginSuccessful = (err == nil)
|
||||||
}
|
}
|
||||||
@ -407,10 +419,15 @@ func nsPasswdHandler(server *Server, client *Client, command, params string, rb
|
|||||||
var newPassword string
|
var newPassword string
|
||||||
var errorMessage string
|
var errorMessage string
|
||||||
|
|
||||||
|
hasPrivs := client.HasRoleCapabs("accreg")
|
||||||
|
if !hasPrivs && !nsLoginThrottleCheck(client, rb) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
fields := strings.Fields(params)
|
fields := strings.Fields(params)
|
||||||
switch len(fields) {
|
switch len(fields) {
|
||||||
case 2:
|
case 2:
|
||||||
if !client.HasRoleCapabs("accreg") {
|
if !hasPrivs {
|
||||||
errorMessage = "Insufficient privileges"
|
errorMessage = "Insufficient privileges"
|
||||||
} else {
|
} else {
|
||||||
target, newPassword = fields[0], fields[1]
|
target, newPassword = fields[0], fields[1]
|
||||||
|
11
oragono.yaml
11
oragono.yaml
@ -179,6 +179,17 @@ accounts:
|
|||||||
# is account authentication enabled?
|
# is account authentication enabled?
|
||||||
authentication-enabled: true
|
authentication-enabled: true
|
||||||
|
|
||||||
|
# throttle account login attempts (to prevent either password guessing, or DoS
|
||||||
|
# attacks on the server aimed at forcing repeated expensive bcrypt computations)
|
||||||
|
login-throttling:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# window
|
||||||
|
duration: 1m
|
||||||
|
|
||||||
|
# number of attempts allowed within the window
|
||||||
|
max-attempts: 3
|
||||||
|
|
||||||
# some clients (notably Pidgin and Hexchat) offer only a single password field,
|
# some clients (notably Pidgin and Hexchat) offer only a single password field,
|
||||||
# which makes it impossible to specify a separate server password (for the PASS
|
# which makes it impossible to specify a separate server password (for the PASS
|
||||||
# command) and SASL password. if this option is set to true, a client that
|
# command) and SASL password. if this option is set to true, a client that
|
||||||
|
Loading…
Reference in New Issue
Block a user