From 3ef4c5f799ae6e4d14406e07a99063dbf8a98af6 Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Sat, 3 Feb 2018 21:38:28 +1000 Subject: [PATCH] Split NS/CS commands into separate functions --- irc/chanserv.go | 100 +++++----- irc/nickserv.go | 479 ++++++++++++++++++++++++------------------------ 2 files changed, 296 insertions(+), 283 deletions(-) diff --git a/irc/chanserv.go b/irc/chanserv.go index 2c9e3b73..e88e74da 100644 --- a/irc/chanserv.go +++ b/irc/chanserv.go @@ -45,55 +45,59 @@ func (server *Server) chanservPrivmsgHandler(client *Client, message string) { return } - if !server.channelRegistrationEnabled { - client.ChanServNotice(client.t("Channel registration is not enabled")) - return - } - - channelName := params[1] - channelKey, err := CasefoldChannel(channelName) - if err != nil { - client.ChanServNotice(client.t("Channel name is not valid")) - return - } - - channelInfo := server.channels.Get(channelKey) - if channelInfo == nil || !channelInfo.ClientIsAtLeast(client, modes.ChannelOperator) { - client.ChanServNotice(client.t("You must be an oper on the channel to register it")) - return - } - - if client.account == &NoAccount { - client.ChanServNotice(client.t("You must be logged in to register a channel")) - return - } - - // this provides the synchronization that allows exactly one registration of the channel: - err = channelInfo.SetRegistered(client.AccountName()) - if err != nil { - client.ChanServNotice(err.Error()) - return - } - - // registration was successful: make the database reflect it - go server.channelRegistry.StoreChannel(channelInfo, true) - - client.ChanServNotice(fmt.Sprintf(client.t("Channel %s successfully registered"), channelName)) - - server.logger.Info("chanserv", fmt.Sprintf("Client %s registered channel %s", client.nick, channelName)) - server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Channel registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), channelName, client.nickMaskString)) - - // give them founder privs - change := channelInfo.applyModeMemberNoMutex(client, modes.ChannelFounder, modes.Add, client.NickCasefolded()) - if change != nil { - //TODO(dan): we should change the name of String and make it return a slice here - //TODO(dan): unify this code with code in modes.go - args := append([]string{channelName}, strings.Split(change.String(), " ")...) - for _, member := range channelInfo.Members() { - member.Send(nil, fmt.Sprintf("ChanServ!services@%s", client.server.name), "MODE", args...) - } - } + server.chanservRegisterHandler(client, params[1]) } else { client.ChanServNotice(client.t("Sorry, I don't know that command")) } } + +// chanservRegisterHandler handles the ChanServ REGISTER subcommand. +func (server *Server) chanservRegisterHandler(client *Client, channelName string) { + if !server.channelRegistrationEnabled { + client.ChanServNotice(client.t("Channel registration is not enabled")) + return + } + + channelKey, err := CasefoldChannel(channelName) + if err != nil { + client.ChanServNotice(client.t("Channel name is not valid")) + return + } + + channelInfo := server.channels.Get(channelKey) + if channelInfo == nil || !channelInfo.ClientIsAtLeast(client, modes.ChannelOperator) { + client.ChanServNotice(client.t("You must be an oper on the channel to register it")) + return + } + + if client.account == &NoAccount { + client.ChanServNotice(client.t("You must be logged in to register a channel")) + return + } + + // this provides the synchronization that allows exactly one registration of the channel: + err = channelInfo.SetRegistered(client.AccountName()) + if err != nil { + client.ChanServNotice(err.Error()) + return + } + + // registration was successful: make the database reflect it + go server.channelRegistry.StoreChannel(channelInfo, true) + + client.ChanServNotice(fmt.Sprintf(client.t("Channel %s successfully registered"), channelName)) + + server.logger.Info("chanserv", fmt.Sprintf("Client %s registered channel %s", client.nick, channelName)) + server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Channel registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), channelName, client.nickMaskString)) + + // give them founder privs + change := channelInfo.applyModeMemberNoMutex(client, modes.ChannelFounder, modes.Add, client.NickCasefolded()) + if change != nil { + //TODO(dan): we should change the name of String and make it return a slice here + //TODO(dan): unify this code with code in modes.go + args := append([]string{channelName}, strings.Split(change.String(), " ")...) + for _, member := range channelInfo.Members() { + member.Send(nil, fmt.Sprintf("ChanServ!services@%s", client.server.name), "MODE", args...) + } + } +} diff --git a/irc/nickserv.go b/irc/nickserv.go index 7141c86d..1f9d0cb6 100644 --- a/irc/nickserv.go +++ b/irc/nickserv.go @@ -62,245 +62,254 @@ func (server *Server) nickservPrivmsgHandler(client *Client, message string) { return } - certfp := client.certfp - if passphrase == "" && certfp == "" { - client.Notice(client.t("You need to either supply a passphrase or be connected via TLS with a client cert")) - return - } - - if !server.accountRegistration.Enabled { - client.Notice(client.t("Account registration has been disabled")) - return - } - - if client.LoggedIntoAccount() { - if server.accountRegistration.AllowMultiplePerConnection { - client.LogoutOfAccount() - } else { - client.Notice(client.t("You're already logged into an account")) - return - } - } - - // get and sanitise account name - account := strings.TrimSpace(username) - casefoldedAccount, err := CasefoldName(account) - // probably don't need explicit check for "*" here... but let's do it anyway just to make sure - if err != nil || username == "*" { - client.Notice(client.t("Account name is not valid")) - return - } - - // check whether account exists - // do it all in one write tx to prevent races - err = server.store.Update(func(tx *buntdb.Tx) error { - accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount) - - _, err := tx.Get(accountKey) - if err != buntdb.ErrNotFound { - //TODO(dan): if account verified key doesn't exist account is not verified, calc the maximum time without verification and expire and continue if need be - client.Notice(client.t("Account already exists")) - return errAccountCreation - } - - registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount) - - tx.Set(accountKey, "1", nil) - tx.Set(fmt.Sprintf(keyAccountName, casefoldedAccount), account, nil) - tx.Set(registeredTimeKey, strconv.FormatInt(time.Now().Unix(), 10), nil) - return nil - }) - - // account could not be created and relevant numerics have been dispatched, abort - if err != nil { - if err != errAccountCreation { - client.Notice(client.t("Account registration failed")) - } - return - } - - // store details - err = server.store.Update(func(tx *buntdb.Tx) error { - // certfp special lookup key - if passphrase == "" { - assembledKeyCertToAccount := fmt.Sprintf(keyCertToAccount, client.certfp) - - // make sure certfp doesn't already exist because that'd be silly - _, err := tx.Get(assembledKeyCertToAccount) - if err != buntdb.ErrNotFound { - return errCertfpAlreadyExists - } - - tx.Set(assembledKeyCertToAccount, casefoldedAccount, nil) - } - - // make creds - var creds AccountCredentials - - // always set passphrase salt - creds.PassphraseSalt, err = passwd.NewSalt() - if err != nil { - return fmt.Errorf("Could not create passphrase salt: %s", err.Error()) - } - - if passphrase == "" { - creds.Certificate = client.certfp - } else { - creds.PassphraseHash, err = server.passwords.GenerateFromPassword(creds.PassphraseSalt, passphrase) - if err != nil { - return fmt.Errorf("Could not hash password: %s", err) - } - } - credText, err := json.Marshal(creds) - if err != nil { - return fmt.Errorf("Could not marshal creds: %s", err) - } - tx.Set(fmt.Sprintf(keyAccountCredentials, account), string(credText), nil) - - return nil - }) - - // details could not be stored and relevant numerics have been dispatched, abort - if err != nil { - errMsg := "Could not register" - if err == errCertfpAlreadyExists { - errMsg = "An account already exists for your certificate fingerprint" - } - client.Notice(errMsg) - removeFailedAccRegisterData(server.store, casefoldedAccount) - return - } - - err = server.store.Update(func(tx *buntdb.Tx) error { - tx.Set(fmt.Sprintf(keyAccountVerified, casefoldedAccount), "1", nil) - - // load acct info inside store tx - account := ClientAccount{ - Name: username, - RegisteredAt: time.Now(), - Clients: []*Client{client}, - } - //TODO(dan): Consider creating ircd-wide account adding/removing/affecting lock for protecting access to these sorts of variables - server.accounts[casefoldedAccount] = &account - client.account = &account - - client.Notice(client.t("Account created")) - client.Send(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, account.Name, fmt.Sprintf(client.t("You are now logged in as %s"), account.Name)) - client.Send(nil, server.name, RPL_SASLSUCCESS, client.nick, client.t("Authentication successful")) - server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Account registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), account.Name, client.nickMaskString)) - return nil - }) - if err != nil { - client.Notice(client.t("Account registration failed")) - removeFailedAccRegisterData(server.store, casefoldedAccount) - return - } - + server.nickservRegisterHandler(client, username, passphrase) } else if command == "identify" { - // fail out if we need to - if !server.accountAuthenticationEnabled { - client.Notice(client.t("Login has been disabled")) - return - } - - // try passphrase + // get params username, passphrase := extractParam(params) - if username != "" && passphrase != "" { - // keep it the same as in the ACC CREATE stage - accountKey, err := CasefoldName(username) - if err != nil { - client.Notice(client.t("Could not login with your username/password")) - return - } - // load and check acct data all in one update to prevent races. - // as noted elsewhere, change to proper locking for Account type later probably - var accountName string - err = server.store.Update(func(tx *buntdb.Tx) error { - // confirm account is verified - _, err = tx.Get(fmt.Sprintf(keyAccountVerified, accountKey)) - if err != nil { - return errSaslFail - } - - creds, err := loadAccountCredentials(tx, accountKey) - if err != nil { - return err - } - - // ensure creds are valid - if len(creds.PassphraseHash) < 1 || len(creds.PassphraseSalt) < 1 || len(passphrase) < 1 { - return errSaslFail - } - err = server.passwords.CompareHashAndPassword(creds.PassphraseHash, creds.PassphraseSalt, passphrase) - - // succeeded, load account info if necessary - account, exists := server.accounts[accountKey] - if !exists { - account = loadAccount(server, tx, accountKey) - } - - client.LoginToAccount(account) - accountName = account.Name - - return err - }) - - if err == nil { - client.Notice(fmt.Sprintf(client.t("You're now logged in as %s"), accountName)) - return - } - } - - // try certfp - certfp := client.certfp - if certfp != "" { - var accountName string - err := server.store.Update(func(tx *buntdb.Tx) error { - // certfp lookup key - accountKey, err := tx.Get(fmt.Sprintf(keyCertToAccount, certfp)) - if err != nil { - return errSaslFail - } - - // confirm account exists - _, err = tx.Get(fmt.Sprintf(keyAccountExists, accountKey)) - if err != nil { - return errSaslFail - } - - // confirm account is verified - _, err = tx.Get(fmt.Sprintf(keyAccountVerified, accountKey)) - if err != nil { - return errSaslFail - } - - // confirm the certfp in that account's credentials - creds, err := loadAccountCredentials(tx, accountKey) - if err != nil || creds.Certificate != client.certfp { - return errSaslFail - } - - // succeeded, load account info if necessary - account, exists := server.accounts[accountKey] - if !exists { - account = loadAccount(server, tx, accountKey) - } - - client.LoginToAccount(account) - accountName = account.Name - - return nil - }) - - if err == nil { - client.Notice(fmt.Sprintf(client.t("You're now logged in as %s"), accountName)) - return - } - } - - client.Notice(client.t("Could not login with your TLS certificate or supplied username/password")) + server.nickservIdentifyHandler(client, username, passphrase) } else { client.Notice(client.t("Command not recognised. To see the available commands, run /NS HELP")) } } + +func (server *Server) nickservRegisterHandler(client *Client, username, passphrase string) { + certfp := client.certfp + if passphrase == "" && certfp == "" { + client.Notice(client.t("You need to either supply a passphrase or be connected via TLS with a client cert")) + return + } + + if !server.accountRegistration.Enabled { + client.Notice(client.t("Account registration has been disabled")) + return + } + + if client.LoggedIntoAccount() { + if server.accountRegistration.AllowMultiplePerConnection { + client.LogoutOfAccount() + } else { + client.Notice(client.t("You're already logged into an account")) + return + } + } + + // get and sanitise account name + account := strings.TrimSpace(username) + casefoldedAccount, err := CasefoldName(account) + // probably don't need explicit check for "*" here... but let's do it anyway just to make sure + if err != nil || username == "*" { + client.Notice(client.t("Account name is not valid")) + return + } + + // check whether account exists + // do it all in one write tx to prevent races + err = server.store.Update(func(tx *buntdb.Tx) error { + accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount) + + _, err := tx.Get(accountKey) + if err != buntdb.ErrNotFound { + //TODO(dan): if account verified key doesn't exist account is not verified, calc the maximum time without verification and expire and continue if need be + client.Notice(client.t("Account already exists")) + return errAccountCreation + } + + registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount) + + tx.Set(accountKey, "1", nil) + tx.Set(fmt.Sprintf(keyAccountName, casefoldedAccount), account, nil) + tx.Set(registeredTimeKey, strconv.FormatInt(time.Now().Unix(), 10), nil) + return nil + }) + + // account could not be created and relevant numerics have been dispatched, abort + if err != nil { + if err != errAccountCreation { + client.Notice(client.t("Account registration failed")) + } + return + } + + // store details + err = server.store.Update(func(tx *buntdb.Tx) error { + // certfp special lookup key + if passphrase == "" { + assembledKeyCertToAccount := fmt.Sprintf(keyCertToAccount, client.certfp) + + // make sure certfp doesn't already exist because that'd be silly + _, err := tx.Get(assembledKeyCertToAccount) + if err != buntdb.ErrNotFound { + return errCertfpAlreadyExists + } + + tx.Set(assembledKeyCertToAccount, casefoldedAccount, nil) + } + + // make creds + var creds AccountCredentials + + // always set passphrase salt + creds.PassphraseSalt, err = passwd.NewSalt() + if err != nil { + return fmt.Errorf("Could not create passphrase salt: %s", err.Error()) + } + + if passphrase == "" { + creds.Certificate = client.certfp + } else { + creds.PassphraseHash, err = server.passwords.GenerateFromPassword(creds.PassphraseSalt, passphrase) + if err != nil { + return fmt.Errorf("Could not hash password: %s", err) + } + } + credText, err := json.Marshal(creds) + if err != nil { + return fmt.Errorf("Could not marshal creds: %s", err) + } + tx.Set(fmt.Sprintf(keyAccountCredentials, account), string(credText), nil) + + return nil + }) + + // details could not be stored and relevant numerics have been dispatched, abort + if err != nil { + errMsg := "Could not register" + if err == errCertfpAlreadyExists { + errMsg = "An account already exists for your certificate fingerprint" + } + client.Notice(errMsg) + removeFailedAccRegisterData(server.store, casefoldedAccount) + return + } + + err = server.store.Update(func(tx *buntdb.Tx) error { + tx.Set(fmt.Sprintf(keyAccountVerified, casefoldedAccount), "1", nil) + + // load acct info inside store tx + account := ClientAccount{ + Name: username, + RegisteredAt: time.Now(), + Clients: []*Client{client}, + } + //TODO(dan): Consider creating ircd-wide account adding/removing/affecting lock for protecting access to these sorts of variables + server.accounts[casefoldedAccount] = &account + client.account = &account + + client.Notice(client.t("Account created")) + client.Send(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, account.Name, fmt.Sprintf(client.t("You are now logged in as %s"), account.Name)) + client.Send(nil, server.name, RPL_SASLSUCCESS, client.nick, client.t("Authentication successful")) + server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Account registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), account.Name, client.nickMaskString)) + return nil + }) + if err != nil { + client.Notice(client.t("Account registration failed")) + removeFailedAccRegisterData(server.store, casefoldedAccount) + return + } +} + +func (server *Server) nickservIdentifyHandler(client *Client, username, passphrase string) { + // fail out if we need to + if !server.accountAuthenticationEnabled { + client.Notice(client.t("Login has been disabled")) + return + } + + // try passphrase + if username != "" && passphrase != "" { + // keep it the same as in the ACC CREATE stage + accountKey, err := CasefoldName(username) + if err != nil { + client.Notice(client.t("Could not login with your username/password")) + return + } + + // load and check acct data all in one update to prevent races. + // as noted elsewhere, change to proper locking for Account type later probably + var accountName string + err = server.store.Update(func(tx *buntdb.Tx) error { + // confirm account is verified + _, err = tx.Get(fmt.Sprintf(keyAccountVerified, accountKey)) + if err != nil { + return errSaslFail + } + + creds, err := loadAccountCredentials(tx, accountKey) + if err != nil { + return err + } + + // ensure creds are valid + if len(creds.PassphraseHash) < 1 || len(creds.PassphraseSalt) < 1 || len(passphrase) < 1 { + return errSaslFail + } + err = server.passwords.CompareHashAndPassword(creds.PassphraseHash, creds.PassphraseSalt, passphrase) + + // succeeded, load account info if necessary + account, exists := server.accounts[accountKey] + if !exists { + account = loadAccount(server, tx, accountKey) + } + + client.LoginToAccount(account) + accountName = account.Name + + return err + }) + + if err == nil { + client.Notice(fmt.Sprintf(client.t("You're now logged in as %s"), accountName)) + return + } + } + + // try certfp + certfp := client.certfp + if certfp != "" { + var accountName string + err := server.store.Update(func(tx *buntdb.Tx) error { + // certfp lookup key + accountKey, err := tx.Get(fmt.Sprintf(keyCertToAccount, certfp)) + if err != nil { + return errSaslFail + } + + // confirm account exists + _, err = tx.Get(fmt.Sprintf(keyAccountExists, accountKey)) + if err != nil { + return errSaslFail + } + + // confirm account is verified + _, err = tx.Get(fmt.Sprintf(keyAccountVerified, accountKey)) + if err != nil { + return errSaslFail + } + + // confirm the certfp in that account's credentials + creds, err := loadAccountCredentials(tx, accountKey) + if err != nil || creds.Certificate != client.certfp { + return errSaslFail + } + + // succeeded, load account info if necessary + account, exists := server.accounts[accountKey] + if !exists { + account = loadAccount(server, tx, accountKey) + } + + client.LoginToAccount(account) + accountName = account.Name + + return nil + }) + + if err == nil { + client.Notice(fmt.Sprintf(client.t("You're now logged in as %s"), accountName)) + return + } + } + + client.Notice(client.t("Could not login with your TLS certificate or supplied username/password")) +}