mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-25 13:29:27 +01:00
per-user settings for nickname enforcement
This commit is contained in:
parent
d0ded906d4
commit
2ee89b15b3
111
irc/accounts.go
111
irc/accounts.go
@ -30,6 +30,7 @@ const (
|
||||
keyAccountRegTime = "account.registered.time %s"
|
||||
keyAccountCredentials = "account.credentials %s"
|
||||
keyAccountAdditionalNicks = "account.additionalnicks %s"
|
||||
keyAccountEnforcement = "account.customenforcement %s"
|
||||
keyAccountVHost = "account.vhost %s"
|
||||
keyCertToAccount = "account.creds.certfp %s"
|
||||
|
||||
@ -53,12 +54,14 @@ type AccountManager struct {
|
||||
// track clients logged in to accounts
|
||||
accountToClients map[string][]*Client
|
||||
nickToAccount map[string]string
|
||||
accountToMethod map[string]NickReservationMethod
|
||||
}
|
||||
|
||||
func NewAccountManager(server *Server) *AccountManager {
|
||||
am := AccountManager{
|
||||
accountToClients: make(map[string][]*Client),
|
||||
nickToAccount: make(map[string]string),
|
||||
accountToMethod: make(map[string]NickReservationMethod),
|
||||
server: server,
|
||||
}
|
||||
|
||||
@ -72,7 +75,8 @@ func (am *AccountManager) buildNickToAccountIndex() {
|
||||
return
|
||||
}
|
||||
|
||||
result := make(map[string]string)
|
||||
nickToAccount := make(map[string]string)
|
||||
accountToMethod := make(map[string]NickReservationMethod)
|
||||
existsPrefix := fmt.Sprintf(keyAccountExists, "")
|
||||
|
||||
am.serialCacheUpdateMutex.Lock()
|
||||
@ -83,14 +87,22 @@ func (am *AccountManager) buildNickToAccountIndex() {
|
||||
if !strings.HasPrefix(key, existsPrefix) {
|
||||
return false
|
||||
}
|
||||
accountName := strings.TrimPrefix(key, existsPrefix)
|
||||
if _, err := tx.Get(fmt.Sprintf(keyAccountVerified, accountName)); err == nil {
|
||||
result[accountName] = accountName
|
||||
|
||||
account := strings.TrimPrefix(key, existsPrefix)
|
||||
if _, err := tx.Get(fmt.Sprintf(keyAccountVerified, account)); err == nil {
|
||||
nickToAccount[account] = account
|
||||
}
|
||||
if rawNicks, err := tx.Get(fmt.Sprintf(keyAccountAdditionalNicks, accountName)); err == nil {
|
||||
if rawNicks, err := tx.Get(fmt.Sprintf(keyAccountAdditionalNicks, account)); err == nil {
|
||||
additionalNicks := unmarshalReservedNicks(rawNicks)
|
||||
for _, nick := range additionalNicks {
|
||||
result[nick] = accountName
|
||||
nickToAccount[nick] = account
|
||||
}
|
||||
}
|
||||
|
||||
if methodStr, err := tx.Get(fmt.Sprintf(keyAccountEnforcement, account)); err == nil {
|
||||
method, err := nickReservationFromString(methodStr)
|
||||
if err == nil {
|
||||
accountToMethod[account] = method
|
||||
}
|
||||
}
|
||||
return true
|
||||
@ -102,7 +114,8 @@ func (am *AccountManager) buildNickToAccountIndex() {
|
||||
am.server.logger.Error("internal", "couldn't read reserved nicks", err.Error())
|
||||
} else {
|
||||
am.Lock()
|
||||
am.nickToAccount = result
|
||||
am.nickToAccount = nickToAccount
|
||||
am.accountToMethod = accountToMethod
|
||||
am.Unlock()
|
||||
}
|
||||
}
|
||||
@ -156,6 +169,84 @@ func (am *AccountManager) NickToAccount(nick string) string {
|
||||
return am.nickToAccount[cfnick]
|
||||
}
|
||||
|
||||
// Given a nick, looks up the account that owns it and the method (none/timeout/strict)
|
||||
// used to enforce ownership.
|
||||
func (am *AccountManager) EnforcementStatus(nick string) (account string, method NickReservationMethod) {
|
||||
cfnick, err := CasefoldName(nick)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
config := am.server.Config()
|
||||
if !config.Accounts.NickReservation.Enabled {
|
||||
method = NickReservationNone
|
||||
return
|
||||
}
|
||||
|
||||
am.RLock()
|
||||
defer am.RUnlock()
|
||||
|
||||
account = am.nickToAccount[cfnick]
|
||||
method = am.accountToMethod[account]
|
||||
// if they don't have a custom setting, or customization is disabled, use the default
|
||||
if method == NickReservationOptional || !config.Accounts.NickReservation.AllowCustomEnforcement {
|
||||
method = config.Accounts.NickReservation.Method
|
||||
}
|
||||
if method == NickReservationOptional {
|
||||
// enforcement was explicitly enabled neither in the config or by the user
|
||||
method = NickReservationNone
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Looks up the enforcement method stored in the database for an account
|
||||
// (typically you want EnforcementStatus instead, which respects the config)
|
||||
func (am *AccountManager) getStoredEnforcementStatus(account string) string {
|
||||
am.RLock()
|
||||
defer am.RUnlock()
|
||||
return nickReservationToString(am.accountToMethod[account])
|
||||
}
|
||||
|
||||
// Sets a custom enforcement method for an account and stores it in the database.
|
||||
func (am *AccountManager) SetEnforcementStatus(account string, method NickReservationMethod) (err error) {
|
||||
config := am.server.Config()
|
||||
if !(config.Accounts.NickReservation.Enabled && config.Accounts.NickReservation.AllowCustomEnforcement) {
|
||||
return errFeatureDisabled
|
||||
}
|
||||
|
||||
var serialized string
|
||||
if method == NickReservationOptional {
|
||||
serialized = "" // normally this is "default", but we're going to delete the key
|
||||
} else {
|
||||
serialized = nickReservationToString(method)
|
||||
}
|
||||
|
||||
key := fmt.Sprintf(keyAccountEnforcement, account)
|
||||
|
||||
am.Lock()
|
||||
defer am.Unlock()
|
||||
|
||||
currentMethod := am.accountToMethod[account]
|
||||
if method != currentMethod {
|
||||
if method == NickReservationOptional {
|
||||
delete(am.accountToMethod, account)
|
||||
} else {
|
||||
am.accountToMethod[account] = method
|
||||
}
|
||||
|
||||
return am.server.store.Update(func(tx *buntdb.Tx) (err error) {
|
||||
if serialized != "" {
|
||||
_, _, err = tx.Set(key, nickReservationToString(method), nil)
|
||||
} else {
|
||||
_, err = tx.Delete(key)
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *AccountManager) AccountToClients(account string) (result []*Client) {
|
||||
cfaccount, err := CasefoldName(account)
|
||||
if err != nil {
|
||||
@ -992,10 +1083,12 @@ func (am *AccountManager) applyVhostToClients(account string, result VHostInfo)
|
||||
|
||||
func (am *AccountManager) Login(client *Client, account ClientAccount) {
|
||||
changed := client.SetAccountName(account.Name)
|
||||
if changed {
|
||||
go client.nickTimer.Touch()
|
||||
if !changed {
|
||||
return
|
||||
}
|
||||
|
||||
client.nickTimer.Touch()
|
||||
|
||||
am.applyVHostInfo(client, account.VHost)
|
||||
|
||||
casefoldedAccount := client.Account()
|
||||
|
@ -119,12 +119,7 @@ func (clients *ClientManager) SetNick(client *Client, newNick string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var reservedAccount string
|
||||
var method NickReservationMethod
|
||||
if client.server.AccountConfig().NickReservation.Enabled {
|
||||
reservedAccount = client.server.accounts.NickToAccount(newcfnick)
|
||||
method = client.server.AccountConfig().NickReservation.Method
|
||||
}
|
||||
reservedAccount, method := client.server.accounts.EnforcementStatus(newcfnick)
|
||||
|
||||
clients.Lock()
|
||||
defer clients.Unlock()
|
||||
|
@ -8,7 +8,6 @@ package irc
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@ -105,10 +104,50 @@ type VHostConfig struct {
|
||||
type NickReservationMethod int
|
||||
|
||||
const (
|
||||
NickReservationWithTimeout NickReservationMethod = iota
|
||||
// NickReservationOptional is the zero value; it serializes to
|
||||
// "optional" in the yaml config, and "default" as an arg to `NS ENFORCE`.
|
||||
// in both cases, it means "defer to the other source of truth", i.e.,
|
||||
// in the config, defer to the user's custom setting, and as a custom setting,
|
||||
// defer to the default in the config. if both are NickReservationOptional then
|
||||
// there is no enforcement.
|
||||
NickReservationOptional NickReservationMethod = iota
|
||||
NickReservationNone
|
||||
NickReservationWithTimeout
|
||||
NickReservationStrict
|
||||
)
|
||||
|
||||
func nickReservationToString(method NickReservationMethod) string {
|
||||
switch method {
|
||||
case NickReservationOptional:
|
||||
return "default"
|
||||
case NickReservationNone:
|
||||
return "none"
|
||||
case NickReservationWithTimeout:
|
||||
return "timeout"
|
||||
case NickReservationStrict:
|
||||
return "strict"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func nickReservationFromString(method string) (NickReservationMethod, error) {
|
||||
switch method {
|
||||
case "default":
|
||||
return NickReservationOptional, nil
|
||||
case "optional":
|
||||
return NickReservationOptional, nil
|
||||
case "none":
|
||||
return NickReservationNone, nil
|
||||
case "timeout":
|
||||
return NickReservationWithTimeout, nil
|
||||
case "strict":
|
||||
return NickReservationStrict, nil
|
||||
default:
|
||||
return NickReservationOptional, fmt.Errorf("invalid nick-reservation.method value: %s", method)
|
||||
}
|
||||
}
|
||||
|
||||
func (nr *NickReservationMethod) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var orig, raw string
|
||||
var err error
|
||||
@ -118,22 +157,20 @@ func (nr *NickReservationMethod) UnmarshalYAML(unmarshal func(interface{}) error
|
||||
if raw, err = Casefold(orig); err != nil {
|
||||
return err
|
||||
}
|
||||
if raw == "timeout" {
|
||||
*nr = NickReservationWithTimeout
|
||||
} else if raw == "strict" {
|
||||
*nr = NickReservationStrict
|
||||
} else {
|
||||
return errors.New(fmt.Sprintf("invalid nick-reservation.method value: %s", orig))
|
||||
method, err := nickReservationFromString(raw)
|
||||
if err == nil {
|
||||
*nr = method
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
type NickReservationConfig struct {
|
||||
Enabled bool
|
||||
AdditionalNickLimit int `yaml:"additional-nick-limit"`
|
||||
Method NickReservationMethod
|
||||
RenameTimeout time.Duration `yaml:"rename-timeout"`
|
||||
RenamePrefix string `yaml:"rename-prefix"`
|
||||
Enabled bool
|
||||
AdditionalNickLimit int `yaml:"additional-nick-limit"`
|
||||
Method NickReservationMethod
|
||||
AllowCustomEnforcement bool `yaml:"allow-custom-enforcement"`
|
||||
RenameTimeout time.Duration `yaml:"rename-timeout"`
|
||||
RenamePrefix string `yaml:"rename-prefix"`
|
||||
}
|
||||
|
||||
// ChannelRegistrationConfig controls channel registration.
|
||||
|
@ -40,6 +40,7 @@ var (
|
||||
errSaslFail = errors.New("SASL failed")
|
||||
errResumeTokenAlreadySet = errors.New("Client was already assigned a resume token")
|
||||
errInvalidUsername = errors.New("Invalid username")
|
||||
errFeatureDisabled = errors.New("That feature is disabled")
|
||||
)
|
||||
|
||||
// Socket Errors
|
||||
|
@ -189,14 +189,14 @@ type NickTimer struct {
|
||||
// NewNickTimer sets up a new nick timer (returning nil if timeout enforcement is not enabled)
|
||||
func NewNickTimer(client *Client) *NickTimer {
|
||||
config := client.server.AccountConfig().NickReservation
|
||||
if !(config.Enabled && config.Method == NickReservationWithTimeout) {
|
||||
if !(config.Enabled && (config.Method == NickReservationWithTimeout || config.AllowCustomEnforcement)) {
|
||||
return nil
|
||||
}
|
||||
nt := NickTimer{
|
||||
|
||||
return &NickTimer{
|
||||
client: client,
|
||||
timeout: config.RenameTimeout,
|
||||
}
|
||||
return &nt
|
||||
}
|
||||
|
||||
// Touch records a nick change and updates the timer as necessary
|
||||
@ -207,7 +207,8 @@ func (nt *NickTimer) Touch() {
|
||||
|
||||
nick := nt.client.NickCasefolded()
|
||||
account := nt.client.Account()
|
||||
accountForNick := nt.client.server.accounts.NickToAccount(nick)
|
||||
accountForNick, method := nt.client.server.accounts.EnforcementStatus(nick)
|
||||
enforceTimeout := method == NickReservationWithTimeout
|
||||
|
||||
var shouldWarn bool
|
||||
|
||||
@ -227,11 +228,11 @@ func (nt *NickTimer) Touch() {
|
||||
nt.accountForNick = accountForNick
|
||||
delinquent := accountForNick != "" && accountForNick != account
|
||||
|
||||
if nt.timer != nil && (!delinquent || accountChanged) {
|
||||
if nt.timer != nil && (!enforceTimeout || !delinquent || accountChanged) {
|
||||
nt.timer.Stop()
|
||||
nt.timer = nil
|
||||
}
|
||||
if delinquent && accountChanged {
|
||||
if enforceTimeout && delinquent && accountChanged {
|
||||
nt.timer = time.AfterFunc(nt.timeout, nt.processTimeout)
|
||||
shouldWarn = true
|
||||
}
|
||||
|
@ -25,6 +25,11 @@ func nsGroupEnabled(server *Server) bool {
|
||||
return conf.Accounts.AuthenticationEnabled && conf.Accounts.NickReservation.Enabled
|
||||
}
|
||||
|
||||
func nsEnforceEnabled(server *Server) bool {
|
||||
config := server.Config()
|
||||
return config.Accounts.NickReservation.Enabled && config.Accounts.NickReservation.AllowCustomEnforcement
|
||||
}
|
||||
|
||||
const nickservHelp = `NickServ lets you register and login to an account.
|
||||
|
||||
To see in-depth help for a specific NickServ command, try:
|
||||
@ -44,6 +49,22 @@ DROP de-links the given (or your current) nickname from your user account.`,
|
||||
enabled: servCmdRequiresAccreg,
|
||||
authRequired: true,
|
||||
},
|
||||
"enforce": {
|
||||
handler: nsEnforceHandler,
|
||||
help: `Syntax: $bENFORCE [method]$b
|
||||
|
||||
ENFORCE lets you specify a custom enforcement mechanism for your registered
|
||||
nicknames. Your options are:
|
||||
1. 'none' [no enforcement, overriding the server default]
|
||||
2. 'timeout' [anyone using the nick must authenticate before a deadline,
|
||||
or else they will be renamed]
|
||||
3. 'strict' [you must already be authenticated to use the nick]
|
||||
4. 'default' [use the server default]
|
||||
With no arguments, queries your current enforcement status.`,
|
||||
helpShort: `$bENFORCE$b lets you change how your nicknames are reserved.`,
|
||||
authRequired: true,
|
||||
enabled: nsEnforceEnabled,
|
||||
},
|
||||
"ghost": {
|
||||
handler: nsGhostHandler,
|
||||
help: `Syntax: $bGHOST <nickname>$b
|
||||
@ -464,3 +485,25 @@ func nsPasswdHandler(server *Server, client *Client, command, params string, rb
|
||||
nsNotice(rb, client.t("Password could not be changed due to server error"))
|
||||
}
|
||||
}
|
||||
|
||||
func nsEnforceHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
|
||||
arg := strings.TrimSpace(params)
|
||||
|
||||
if arg == "" {
|
||||
status := server.accounts.getStoredEnforcementStatus(client.Account())
|
||||
nsNotice(rb, fmt.Sprintf(client.t("Your current nickname enforcement is: %s"), status))
|
||||
} else {
|
||||
method, err := nickReservationFromString(arg)
|
||||
if err != nil {
|
||||
nsNotice(rb, client.t("Invalid parameters"))
|
||||
return
|
||||
}
|
||||
err = server.accounts.SetEnforcementStatus(client.Account(), method)
|
||||
if err == nil {
|
||||
nsNotice(rb, client.t("Enforcement method set"))
|
||||
} else {
|
||||
server.logger.Error("internal", "couldn't store NS ENFORCE data", err.Error())
|
||||
nsNotice(rb, client.t("An error occurred"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
15
oragono.yaml
15
oragono.yaml
@ -206,12 +206,19 @@ accounts:
|
||||
additional-nick-limit: 2
|
||||
|
||||
# method describes how nickname reservation is handled
|
||||
# timeout: let the user change to the registered nickname, give them X seconds
|
||||
# to login and then rename them if they haven't done so
|
||||
# strict: don't let the user change to the registered nickname unless they're
|
||||
# already logged-in using SASL or NickServ
|
||||
# already logged-in using SASL or NickServ
|
||||
# timeout: let the user change to the registered nickname, give them X seconds
|
||||
# to login and then rename them if they haven't done so
|
||||
# strict: don't let the user change to the registered nickname unless they're
|
||||
# already logged-in using SASL or NickServ
|
||||
# optional: no enforcement by default, but allow users to opt in to
|
||||
# the enforcement level of their choice
|
||||
method: timeout
|
||||
|
||||
# allow users to set their own nickname enforcement status, e.g.,
|
||||
# to opt in to strict enforcement
|
||||
allow-custom-enforcement: true
|
||||
|
||||
# rename-timeout - this is how long users have 'til they're renamed
|
||||
rename-timeout: 30s
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user