diff --git a/irc/accounts.go b/irc/accounts.go index 124df613..e4517c80 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -935,6 +935,12 @@ func (am *AccountManager) checkPassphrase(accountName, passphrase string) (accou } func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName string, passphrase string) (err error) { + if client.registered { + if clientAlready := am.server.clients.Get(accountName); clientAlready != nil && clientAlready.AlwaysOn() { + return errNickAccountMismatch + } + } + var account ClientAccount defer func() { @@ -1210,6 +1216,11 @@ func (am *AccountManager) AuthenticateByCertFP(client *Client, certfp, authzid s } else if !clientAccount.Verified { return errAccountUnverified } + if client.registered { + if clientAlready := am.server.clients.Get(clientAccount.Name); clientAlready != nil && clientAlready.AlwaysOn() { + return errNickAccountMismatch + } + } am.Login(client, clientAccount) return nil } diff --git a/irc/errors.go b/irc/errors.go index de1e5b91..f5d9a2be 100644 --- a/irc/errors.go +++ b/irc/errors.go @@ -42,6 +42,7 @@ var ( errNicknameInUse = errors.New("nickname in use") errNicknameReserved = errors.New("nickname is reserved") errCantChangeNick = errors.New(`Always-on clients can't change nicknames`) + errNickAccountMismatch = errors.New(`Your nickname doesn't match your account name`) errNoExistingBan = errors.New("Ban does not exist") errNoSuchChannel = errors.New(`No such channel`) errChannelPurged = errors.New(`This channel was purged by the server operators and cannot be used`) diff --git a/irc/nickserv.go b/irc/nickserv.go index beffee69..8981d7dd 100644 --- a/irc/nickserv.go +++ b/irc/nickserv.go @@ -471,14 +471,26 @@ func nsSetHandler(server *Server, client *Client, command string, params []strin } } case "always-on": - var newValue PersistentStatus - newValue, err = persistentStatusFromString(params[1]) - // "opt-in" and "opt-out" don't make sense as user preferences - if err == nil && newValue != PersistentOptIn && newValue != PersistentOptOut { - munger = func(in AccountSettings) (out AccountSettings, err error) { - out = in - out.AlwaysOn = newValue - return + // #821: it's problematic to alter the value of always-on if you're not + // the (actual or potential) always-on client yourself. make an exception + // for `saset` to give operators an escape hatch (any consistency problems + // can probably be fixed by restarting the server): + if command != "saset" { + details := client.Details() + if details.nick != details.accountName { + err = errNickAccountMismatch + } + } + if err == nil { + var newValue PersistentStatus + newValue, err = persistentStatusFromString(params[1]) + // "opt-in" and "opt-out" don't make sense as user preferences + if err == nil && newValue != PersistentOptIn && newValue != PersistentOptOut { + munger = func(in AccountSettings) (out AccountSettings, err error) { + out = in + out.AlwaysOn = newValue + return + } } } case "autoreplay-missed": @@ -515,6 +527,8 @@ func nsSetHandler(server *Server, client *Client, command string, params []strin displaySetting(params[0], finalSettings, client, rb) case errInvalidParams, errAccountDoesNotExist, errFeatureDisabled, errAccountUnverified, errAccountUpdateFailed: nsNotice(rb, client.t(err.Error())) + case errNickAccountMismatch: + nsNotice(rb, fmt.Sprintf(client.t("Your nickname must match your account name %s exactly to modify this setting. Try changing it with /NICK, or logging out and back in with the correct nickname."), client.AccountName())) default: // unknown error nsNotice(rb, client.t("An error occurred")) @@ -601,6 +615,7 @@ func nsIdentifyHandler(server *Server, client *Client, command string, params [] return } + var err error loginSuccessful := false var username, passphrase string @@ -623,18 +638,20 @@ func nsIdentifyHandler(server *Server, client *Client, command string, params [] if !nsLoginThrottleCheck(client, rb) { return } - err := server.accounts.AuthenticateByPassphrase(client, username, passphrase) + err = server.accounts.AuthenticateByPassphrase(client, username, passphrase) loginSuccessful = (err == nil) } // try certfp if !loginSuccessful && rb.session.certfp != "" { - err := server.accounts.AuthenticateByCertFP(client, rb.session.certfp, "") + err = server.accounts.AuthenticateByCertFP(client, rb.session.certfp, "") loginSuccessful = (err == nil) } if loginSuccessful { sendSuccessfulAccountAuth(client, rb, true, true) + } else if err == errNickAccountMismatch { + nsNotice(rb, client.t("That account is set to always-on; try logging out and logging back in with SASL")) } else { nsNotice(rb, client.t("Could not login with your TLS certificate or supplied username/password")) } diff --git a/irc/server.go b/irc/server.go index 2ffe41c9..ca1ea485 100644 --- a/irc/server.go +++ b/irc/server.go @@ -584,6 +584,8 @@ func (server *Server) applyConfig(config *Config) (err error) { return fmt.Errorf("Datastore path cannot be changed after launching the server, rehash aborted") } else if globalCasemappingSetting != config.Server.Casemapping { return fmt.Errorf("Casemapping cannot be changed after launching the server, rehash aborted") + } else if oldConfig.Accounts.Multiclient.AlwaysOn != config.Accounts.Multiclient.AlwaysOn { + return fmt.Errorf("Default always-on setting cannot be changed after launching the server, rehash aborted") } }