diff --git a/irc/accounts.go b/irc/accounts.go index 9529c45f..6b8dfba4 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -51,7 +51,7 @@ func NewAccountManager(server *Server) *AccountManager { } func (am *AccountManager) buildNickToAccountIndex() { - if am.server.AccountConfig().NickReservation == NickReservationDisabled { + if am.server.AccountConfig().NickReservation.Enabled { return } @@ -98,6 +98,12 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames return errAccountCreation } + // can't register a guest nickname + renamePrefix := strings.ToLower(am.server.AccountConfig().NickReservation.RenamePrefix) + if renamePrefix != "" && strings.HasPrefix(casefoldedAccount, renamePrefix) { + return errAccountAlreadyRegistered + } + accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount) accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount) registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount) diff --git a/irc/client_lookup_set.go b/irc/client_lookup_set.go index 005847f6..953fb591 100644 --- a/irc/client_lookup_set.go +++ b/irc/client_lookup_set.go @@ -99,9 +99,10 @@ func (clients *ClientManager) SetNick(client *Client, newNick string) error { } var reservedAccount string - reservation := client.server.AccountConfig().NickReservation - if reservation != NickReservationDisabled { + var method NickReservationMethod + if client.server.AccountConfig().NickReservation.Enabled { reservedAccount = client.server.accounts.NickToAccount(newcfnick) + method = client.server.AccountConfig().NickReservation.Method } clients.Lock() @@ -113,7 +114,7 @@ func (clients *ClientManager) SetNick(client *Client, newNick string) error { if currentNewEntry != nil && currentNewEntry != client { return errNicknameInUse } - if reservation == NickReservationStrict && reservedAccount != client.Account() { + if method == NickReservationStrict && reservedAccount != client.Account() { return errNicknameReserved } clients.byNick[newcfnick] = client diff --git a/irc/config.go b/irc/config.go index 06f649ad..34155a47 100644 --- a/irc/config.go +++ b/irc/config.go @@ -58,40 +58,10 @@ func (conf *PassConfig) PasswordBytes() []byte { return bytes } -type NickReservation int - -const ( - NickReservationDisabled NickReservation = iota - NickReservationWithTimeout - NickReservationStrict -) - -func (nr *NickReservation) UnmarshalYAML(unmarshal func(interface{}) error) error { - var orig, raw string - var err error - if err = unmarshal(&orig); err != nil { - return err - } - if raw, err = Casefold(orig); err != nil { - return err - } - if raw == "disabled" || raw == "false" || raw == "" { - *nr = NickReservationDisabled - } else if raw == "timeout" { - *nr = NickReservationWithTimeout - } else if raw == "strict" { - *nr = NickReservationStrict - } else { - return errors.New(fmt.Sprintf("invalid nick-reservation value: %s", orig)) - } - return nil -} - type AccountConfig struct { - Registration AccountRegistrationConfig - AuthenticationEnabled bool `yaml:"authentication-enabled"` - NickReservation NickReservation `yaml:"nick-reservation"` - NickReservationTimeout time.Duration `yaml:"nick-reservation-timeout"` + Registration AccountRegistrationConfig + AuthenticationEnabled bool `yaml:"authentication-enabled"` + NickReservation NickReservationConfig `yaml:"nick-reservation"` } // AccountRegistrationConfig controls account registration. @@ -119,6 +89,39 @@ type AccountRegistrationConfig struct { AllowMultiplePerConnection bool `yaml:"allow-multiple-per-connection"` } +type NickReservationMethod int + +const ( + NickReservationWithTimeout NickReservationMethod = iota + NickReservationStrict +) + +func (nr *NickReservationMethod) UnmarshalYAML(unmarshal func(interface{}) error) error { + var orig, raw string + var err error + if err = unmarshal(&orig); err != nil { + return err + } + 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)) + } + return nil +} + +type NickReservationConfig struct { + Enabled bool + Method NickReservationMethod + RenameTimeout time.Duration `yaml:"rename-timeout"` + RenamePrefix string `yaml:"rename-prefix"` +} + // ChannelRegistrationConfig controls channel registration. type ChannelRegistrationConfig struct { Enabled bool diff --git a/irc/idletimer.go b/irc/idletimer.go index 7d5dcd9b..77a3eb20 100644 --- a/irc/idletimer.go +++ b/irc/idletimer.go @@ -183,13 +183,13 @@ 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() - if config.NickReservation != NickReservationWithTimeout { + config := client.server.AccountConfig().NickReservation + if !(config.Enabled && config.Method == NickReservationWithTimeout) { return nil } nt := NickTimer{ client: client, - timeout: config.NickReservationTimeout, + timeout: config.RenameTimeout, } return &nt } @@ -239,6 +239,6 @@ func (nt *NickTimer) sendWarning() { func (nt *NickTimer) processTimeout() { baseMsg := "Nick is reserved and authentication timeout expired: %v" - nt.client.Quit(fmt.Sprintf(nt.client.t(baseMsg), nt.timeout)) - nt.client.destroy(false) + nt.client.Notice(fmt.Sprintf(nt.client.t(baseMsg), nt.timeout)) + nt.client.server.RandomlyRename(nt.client) } diff --git a/irc/nickname.go b/irc/nickname.go index 09409c82..b0728b7a 100644 --- a/irc/nickname.go +++ b/irc/nickname.go @@ -5,6 +5,8 @@ package irc import ( + "crypto/rand" + "encoding/hex" "fmt" "strings" @@ -72,3 +74,18 @@ func performNickChange(server *Server, client *Client, target *Client, newnick s } return false } + +func (server *Server) RandomlyRename(client *Client) { + prefix := server.AccountConfig().NickReservation.RenamePrefix + if prefix == "" { + prefix = "Guest-" + } + buf := make([]byte, 8) + rand.Read(buf) + nick := fmt.Sprintf("%s%s", prefix, hex.EncodeToString(buf)) + rb := NewResponseBuffer(client) + performNickChange(server, client, client, nick, rb) + rb.Send() + // technically performNickChange can fail to change the nick, + // but if they're still delinquent, the timer will get them later +} diff --git a/irc/server.go b/irc/server.go index 051a36c7..f920e629 100644 --- a/irc/server.go +++ b/irc/server.go @@ -810,8 +810,8 @@ func (server *Server) applyConfig(config *Config, initial bool) error { server.accountConfig = &config.Accounts server.configurableStateMutex.Unlock() - nickReservationPreviouslyDisabled := oldAccountConfig != nil && oldAccountConfig.NickReservation == NickReservationDisabled - nickReservationNowEnabled := config.Accounts.NickReservation != NickReservationDisabled + nickReservationPreviouslyDisabled := oldAccountConfig != nil && !oldAccountConfig.NickReservation.Enabled + nickReservationNowEnabled := config.Accounts.NickReservation.Enabled if nickReservationPreviouslyDisabled && nickReservationNowEnabled { server.accounts.buildNickToAccountIndex() } @@ -1113,13 +1113,6 @@ func (server *Server) setupListeners(config *Config) { } } -// GetDefaultChannelModes returns our default channel modes. -func (server *Server) GetDefaultChannelModes() modes.Modes { - server.configurableStateMutex.RLock() - defer server.configurableStateMutex.RUnlock() - return server.defaultChannelModes -} - // elistMatcher takes and matches ELIST conditions type elistMatcher struct { MinClientsActive bool diff --git a/oragono.yaml b/oragono.yaml index 8d614aae..cc68e87d 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -159,13 +159,23 @@ accounts: # is account authentication enabled? authentication-enabled: true - # will the server enforce that only the account holder can use the account name as a nick? - # options: - # `disabled`: no enforcement - # `timeout` (auth to nickserv within some period of time or you're disconnected) - # `strict`: must authenticate up front with SASL - nick-reservation: disabled - nick-reservation-timeout: 30s + # nick-reservation controls how, and whether, nicknames are linked to accounts + nick-reservation: + # is there any enforcement of reserved nicknames? + enabled: false + + # 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 + method: timeout + + # rename-timeout - this is how long users have 'til they're renamed + rename-timeout: 30s + + # rename-prefix - this is the prefix to use when renaming clients (e.g. Guest-AB54U31) + rename-prefix: Guest- # channel options channels: