mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-22 03:49:27 +01:00
refactor account registration, add nick enforcement
This commit is contained in:
parent
fcd0a75469
commit
ad73d68807
@ -98,7 +98,7 @@ In consequence, there is a lot of state (in particular, server and channel state
|
|||||||
|
|
||||||
There are some mutexes that are "tier 0": anything in a subpackage of `irc` (e.g., `irc/logger` or `irc/connection_limits`) shouldn't acquire mutexes defined in `irc`.
|
There are some mutexes that are "tier 0": anything in a subpackage of `irc` (e.g., `irc/logger` or `irc/connection_limits`) shouldn't acquire mutexes defined in `irc`.
|
||||||
|
|
||||||
We are using `buntdb` for persistence; a `buntdb.DB` has an `RWMutex` inside it, with read-write transactions getting the `Lock()` and read-only transactions getting the `RLock()`. We haven't completely decided where this lock fits into the overall lock model. For now, it's probably better to err on the side of caution: if possible, don't acquire new locks inside the `buntdb` transaction, and be careful about what locks are held around the transaction as well.
|
We are using `buntdb` for persistence; a `buntdb.DB` has an `RWMutex` inside it, with read-write transactions getting the `Lock()` and read-only transactions getting the `RLock()`. This mutex is considered tier 1. However, it's shared globally across all consumers, so if possible you should avoid acquiring it while holding ordinary application-level mutexes.
|
||||||
|
|
||||||
## Command handlers and ResponseBuffer
|
## Command handlers and ResponseBuffer
|
||||||
|
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
|
|
||||||
// released under the MIT license
|
|
||||||
|
|
||||||
package irc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/tidwall/buntdb"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AccountRegistration manages the registration of accounts.
|
|
||||||
type AccountRegistration struct {
|
|
||||||
Enabled bool
|
|
||||||
EnabledCallbacks []string
|
|
||||||
EnabledCredentialTypes []string
|
|
||||||
AllowMultiplePerConnection bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// AccountCredentials stores the various methods for verifying accounts.
|
|
||||||
type AccountCredentials struct {
|
|
||||||
PassphraseSalt []byte
|
|
||||||
PassphraseHash []byte
|
|
||||||
Certificate string // fingerprint
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAccountRegistration returns a new AccountRegistration, configured correctly.
|
|
||||||
func NewAccountRegistration(config AccountRegistrationConfig) (accountReg AccountRegistration) {
|
|
||||||
if config.Enabled {
|
|
||||||
accountReg.Enabled = true
|
|
||||||
accountReg.AllowMultiplePerConnection = config.AllowMultiplePerConnection
|
|
||||||
for _, name := range config.EnabledCallbacks {
|
|
||||||
// we store "none" as "*" internally
|
|
||||||
if name == "none" {
|
|
||||||
name = "*"
|
|
||||||
}
|
|
||||||
accountReg.EnabledCallbacks = append(accountReg.EnabledCallbacks, name)
|
|
||||||
}
|
|
||||||
// no need to make this configurable, right now at least
|
|
||||||
accountReg.EnabledCredentialTypes = []string{
|
|
||||||
"passphrase",
|
|
||||||
"certfp",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return accountReg
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeFailedAccRegisterData removes the data created by ACC REGISTER if the account creation fails early.
|
|
||||||
func removeFailedAccRegisterData(store *buntdb.DB, account string) {
|
|
||||||
// error is ignored here, we can't do much about it anyways
|
|
||||||
store.Update(func(tx *buntdb.Tx) error {
|
|
||||||
tx.Delete(fmt.Sprintf(keyAccountExists, account))
|
|
||||||
tx.Delete(fmt.Sprintf(keyAccountRegTime, account))
|
|
||||||
tx.Delete(fmt.Sprintf(keyAccountCredentials, account))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
517
irc/accounts.go
517
irc/accounts.go
@ -7,10 +7,13 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/goshuirc/irc-go/ircfmt"
|
"github.com/goshuirc/irc-go/ircfmt"
|
||||||
"github.com/oragono/oragono/irc/caps"
|
"github.com/oragono/oragono/irc/caps"
|
||||||
|
"github.com/oragono/oragono/irc/passwd"
|
||||||
"github.com/oragono/oragono/irc/sno"
|
"github.com/oragono/oragono/irc/sno"
|
||||||
"github.com/tidwall/buntdb"
|
"github.com/tidwall/buntdb"
|
||||||
)
|
)
|
||||||
@ -24,6 +27,423 @@ const (
|
|||||||
keyCertToAccount = "account.creds.certfp %s"
|
keyCertToAccount = "account.creds.certfp %s"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// everything about accounts is persistent; therefore, the database is the authoritative
|
||||||
|
// source of truth for all account information. anything on the heap is just a cache
|
||||||
|
type AccountManager struct {
|
||||||
|
sync.RWMutex // tier 2
|
||||||
|
serialCacheUpdateMutex sync.Mutex // tier 3
|
||||||
|
|
||||||
|
server *Server
|
||||||
|
// track clients logged in to accounts
|
||||||
|
accountToClients map[string][]*Client
|
||||||
|
nickToAccount map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccountManager(server *Server) *AccountManager {
|
||||||
|
am := AccountManager{
|
||||||
|
accountToClients: make(map[string][]*Client),
|
||||||
|
nickToAccount: make(map[string]string),
|
||||||
|
server: server,
|
||||||
|
}
|
||||||
|
|
||||||
|
am.buildNickToAccountIndex()
|
||||||
|
return &am
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) buildNickToAccountIndex() {
|
||||||
|
if am.server.AccountConfig().NickReservation == NickReservationDisabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[string]string)
|
||||||
|
existsPrefix := fmt.Sprintf(keyAccountExists, "")
|
||||||
|
|
||||||
|
am.serialCacheUpdateMutex.Lock()
|
||||||
|
defer am.serialCacheUpdateMutex.Unlock()
|
||||||
|
|
||||||
|
err := am.server.store.View(func(tx *buntdb.Tx) error {
|
||||||
|
err := tx.AscendGreaterOrEqual("", existsPrefix, func(key, value string) bool {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
am.server.logger.Error("internal", fmt.Sprintf("couldn't read reserved nicks: %v", err))
|
||||||
|
} else {
|
||||||
|
am.Lock()
|
||||||
|
am.nickToAccount = result
|
||||||
|
am.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) NickToAccount(cfnick string) string {
|
||||||
|
am.RLock()
|
||||||
|
defer am.RUnlock()
|
||||||
|
return am.nickToAccount[cfnick]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) Register(client *Client, account string, callbackNamespace string, callbackValue string, passphrase string, certfp string) error {
|
||||||
|
casefoldedAccount, err := CasefoldName(account)
|
||||||
|
if err != nil || account == "" || account == "*" {
|
||||||
|
return errAccountCreation
|
||||||
|
}
|
||||||
|
|
||||||
|
accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
|
||||||
|
accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
|
||||||
|
registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
|
||||||
|
credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
|
||||||
|
certFPKey := fmt.Sprintf(keyCertToAccount, certfp)
|
||||||
|
|
||||||
|
var creds AccountCredentials
|
||||||
|
// always set passphrase salt
|
||||||
|
creds.PassphraseSalt, err = passwd.NewSalt()
|
||||||
|
if err != nil {
|
||||||
|
return errAccountCreation
|
||||||
|
}
|
||||||
|
// it's fine if this is empty, that just means no certificate is authorized
|
||||||
|
creds.Certificate = certfp
|
||||||
|
if passphrase != "" {
|
||||||
|
creds.PassphraseHash, err = am.server.passwords.GenerateFromPassword(creds.PassphraseSalt, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
am.server.logger.Error("internal", fmt.Sprintf("could not hash password: %v", err))
|
||||||
|
return errAccountCreation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
credText, err := json.Marshal(creds)
|
||||||
|
if err != nil {
|
||||||
|
am.server.logger.Error("internal", fmt.Sprintf("could not marshal credentials: %v", err))
|
||||||
|
return errAccountCreation
|
||||||
|
}
|
||||||
|
credStr := string(credText)
|
||||||
|
|
||||||
|
registeredTimeStr := strconv.FormatInt(time.Now().Unix(), 10)
|
||||||
|
|
||||||
|
var setOptions *buntdb.SetOptions
|
||||||
|
ttl := am.server.AccountConfig().Registration.VerifyTimeout
|
||||||
|
if ttl != 0 {
|
||||||
|
setOptions = &buntdb.SetOptions{Expires: true, TTL: ttl}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||||
|
_, err := am.loadRawAccount(tx, casefoldedAccount)
|
||||||
|
if err != errAccountDoesNotExist {
|
||||||
|
return errAccountAlreadyRegistered
|
||||||
|
}
|
||||||
|
|
||||||
|
if certfp != "" {
|
||||||
|
// make sure certfp doesn't already exist because that'd be silly
|
||||||
|
_, err := tx.Get(certFPKey)
|
||||||
|
if err != buntdb.ErrNotFound {
|
||||||
|
return errCertfpAlreadyExists
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Set(accountKey, "1", setOptions)
|
||||||
|
tx.Set(accountNameKey, account, setOptions)
|
||||||
|
tx.Set(registeredTimeKey, registeredTimeStr, setOptions)
|
||||||
|
tx.Set(credentialsKey, credStr, setOptions)
|
||||||
|
if certfp != "" {
|
||||||
|
tx.Set(certFPKey, casefoldedAccount, setOptions)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) Verify(client *Client, account string, code string) error {
|
||||||
|
casefoldedAccount, err := CasefoldName(account)
|
||||||
|
if err != nil || account == "" || account == "*" {
|
||||||
|
return errAccountVerificationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
|
||||||
|
accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
|
||||||
|
accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
|
||||||
|
registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
|
||||||
|
credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
|
||||||
|
|
||||||
|
var raw rawClientAccount
|
||||||
|
|
||||||
|
func() {
|
||||||
|
am.serialCacheUpdateMutex.Lock()
|
||||||
|
defer am.serialCacheUpdateMutex.Unlock()
|
||||||
|
|
||||||
|
am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||||
|
raw, err = am.loadRawAccount(tx, casefoldedAccount)
|
||||||
|
if err == errAccountDoesNotExist {
|
||||||
|
return errAccountDoesNotExist
|
||||||
|
} else if err != nil {
|
||||||
|
return errAccountVerificationFailed
|
||||||
|
} else if raw.Verified {
|
||||||
|
return errAccountAlreadyVerified
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add code verification here
|
||||||
|
// return errAccountVerificationFailed if it fails
|
||||||
|
|
||||||
|
// verify the account
|
||||||
|
tx.Set(verifiedKey, "1", nil)
|
||||||
|
// re-set all other keys, removing the TTL
|
||||||
|
tx.Set(accountKey, "1", nil)
|
||||||
|
tx.Set(accountNameKey, raw.Name, nil)
|
||||||
|
tx.Set(registeredTimeKey, raw.RegisteredAt, nil)
|
||||||
|
tx.Set(credentialsKey, raw.Credentials, nil)
|
||||||
|
|
||||||
|
var creds AccountCredentials
|
||||||
|
// XXX we shouldn't do (de)serialization inside the txn,
|
||||||
|
// but this is like 2 usec on my system
|
||||||
|
json.Unmarshal([]byte(raw.Credentials), &creds)
|
||||||
|
if creds.Certificate != "" {
|
||||||
|
certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
|
||||||
|
tx.Set(certFPKey, casefoldedAccount, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
am.Lock()
|
||||||
|
am.nickToAccount[casefoldedAccount] = casefoldedAccount
|
||||||
|
am.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
am.Login(client, raw.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName string, passphrase string) error {
|
||||||
|
casefoldedAccount, err := CasefoldName(accountName)
|
||||||
|
if err != nil {
|
||||||
|
return errAccountDoesNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := am.LoadAccount(casefoldedAccount)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !account.Verified {
|
||||||
|
return errAccountUnverified
|
||||||
|
}
|
||||||
|
|
||||||
|
err = am.server.passwords.CompareHashAndPassword(
|
||||||
|
account.Credentials.PassphraseHash, account.Credentials.PassphraseSalt, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return errAccountInvalidCredentials
|
||||||
|
}
|
||||||
|
|
||||||
|
am.Login(client, account.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) LoadAccount(casefoldedAccount string) (result ClientAccount, err error) {
|
||||||
|
var raw rawClientAccount
|
||||||
|
am.server.store.View(func(tx *buntdb.Tx) error {
|
||||||
|
raw, err = am.loadRawAccount(tx, casefoldedAccount)
|
||||||
|
if err == buntdb.ErrNotFound {
|
||||||
|
err = errAccountDoesNotExist
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Name = raw.Name
|
||||||
|
regTimeInt, _ := strconv.ParseInt(raw.RegisteredAt, 10, 64)
|
||||||
|
result.RegisteredAt = time.Unix(regTimeInt, 0)
|
||||||
|
e := json.Unmarshal([]byte(raw.Credentials), &result.Credentials)
|
||||||
|
if e != nil {
|
||||||
|
am.server.logger.Error("internal", fmt.Sprintf("could not unmarshal credentials: %v", e))
|
||||||
|
err = errAccountDoesNotExist
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result.Verified = raw.Verified
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string) (result rawClientAccount, err error) {
|
||||||
|
accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
|
||||||
|
accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
|
||||||
|
registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
|
||||||
|
credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
|
||||||
|
verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
|
||||||
|
|
||||||
|
_, e := tx.Get(accountKey)
|
||||||
|
if e == buntdb.ErrNotFound {
|
||||||
|
err = errAccountDoesNotExist
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Name, err = tx.Get(accountNameKey); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result.RegisteredAt, err = tx.Get(registeredTimeKey); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result.Credentials, err = tx.Get(credentialsKey); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, e = tx.Get(verifiedKey); e == nil {
|
||||||
|
result.Verified = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) Unregister(account string) error {
|
||||||
|
casefoldedAccount, err := CasefoldName(account)
|
||||||
|
if err != nil {
|
||||||
|
return errAccountDoesNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
|
||||||
|
accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
|
||||||
|
registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
|
||||||
|
credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
|
||||||
|
verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
|
||||||
|
|
||||||
|
var clients []*Client
|
||||||
|
|
||||||
|
func() {
|
||||||
|
var credText string
|
||||||
|
|
||||||
|
am.serialCacheUpdateMutex.Lock()
|
||||||
|
defer am.serialCacheUpdateMutex.Unlock()
|
||||||
|
|
||||||
|
am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||||
|
tx.Delete(accountKey)
|
||||||
|
tx.Delete(accountNameKey)
|
||||||
|
tx.Delete(verifiedKey)
|
||||||
|
tx.Delete(registeredTimeKey)
|
||||||
|
credText, err = tx.Get(credentialsKey)
|
||||||
|
tx.Delete(credentialsKey)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
var creds AccountCredentials
|
||||||
|
if err = json.Unmarshal([]byte(credText), &creds); err == nil && creds.Certificate != "" {
|
||||||
|
certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
|
||||||
|
am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||||
|
if account, err := tx.Get(certFPKey); err == nil && account == casefoldedAccount {
|
||||||
|
tx.Delete(certFPKey)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
am.Lock()
|
||||||
|
defer am.Unlock()
|
||||||
|
clients = am.accountToClients[casefoldedAccount]
|
||||||
|
delete(am.accountToClients, casefoldedAccount)
|
||||||
|
// TODO when registration of multiple nicks is fully implemented,
|
||||||
|
// save the nicks that were deleted from the store and delete them here:
|
||||||
|
delete(am.nickToAccount, casefoldedAccount)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for _, client := range clients {
|
||||||
|
client.LogoutOfAccount()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) AuthenticateByCertFP(client *Client) error {
|
||||||
|
if client.certfp == "" {
|
||||||
|
return errAccountInvalidCredentials
|
||||||
|
}
|
||||||
|
|
||||||
|
var account string
|
||||||
|
var rawAccount rawClientAccount
|
||||||
|
certFPKey := fmt.Sprintf(keyCertToAccount, client.certfp)
|
||||||
|
|
||||||
|
err := am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||||
|
var err error
|
||||||
|
account, _ = tx.Get(certFPKey)
|
||||||
|
if account == "" {
|
||||||
|
return errAccountInvalidCredentials
|
||||||
|
}
|
||||||
|
rawAccount, err = am.loadRawAccount(tx, account)
|
||||||
|
if err != nil || !rawAccount.Verified {
|
||||||
|
return errAccountUnverified
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ok, we found an account corresponding to their certificate
|
||||||
|
|
||||||
|
am.Login(client, rawAccount.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) Login(client *Client, account string) {
|
||||||
|
client.LoginToAccount(account)
|
||||||
|
|
||||||
|
casefoldedAccount, _ := CasefoldName(account)
|
||||||
|
am.Lock()
|
||||||
|
defer am.Unlock()
|
||||||
|
am.accountToClients[casefoldedAccount] = append(am.accountToClients[casefoldedAccount], client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) Logout(client *Client) {
|
||||||
|
casefoldedAccount := client.Account()
|
||||||
|
if casefoldedAccount == "" || casefoldedAccount == "*" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client.LogoutOfAccount()
|
||||||
|
|
||||||
|
am.Lock()
|
||||||
|
defer am.Unlock()
|
||||||
|
|
||||||
|
if client.LoggedIntoAccount() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
clients := am.accountToClients[casefoldedAccount]
|
||||||
|
if len(clients) <= 1 {
|
||||||
|
delete(am.accountToClients, casefoldedAccount)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
remainingClients := make([]*Client, len(clients)-1)
|
||||||
|
remainingPos := 0
|
||||||
|
for currentPos := 0; currentPos < len(clients); currentPos++ {
|
||||||
|
if clients[currentPos] != client {
|
||||||
|
remainingClients[remainingPos] = clients[currentPos]
|
||||||
|
remainingPos++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
am.accountToClients[casefoldedAccount] = remainingClients
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// EnabledSaslMechanisms contains the SASL mechanisms that exist and that we support.
|
// EnabledSaslMechanisms contains the SASL mechanisms that exist and that we support.
|
||||||
// This can be moved to some other data structure/place if we need to load/unload mechs later.
|
// This can be moved to some other data structure/place if we need to load/unload mechs later.
|
||||||
@ -31,95 +451,62 @@ var (
|
|||||||
"PLAIN": authPlainHandler,
|
"PLAIN": authPlainHandler,
|
||||||
"EXTERNAL": authExternalHandler,
|
"EXTERNAL": authExternalHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoAccount is a placeholder which means that the user is not logged into an account.
|
|
||||||
NoAccount = ClientAccount{
|
|
||||||
Name: "*", // * is used until actual account name is set
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AccountCredentials stores the various methods for verifying accounts.
|
||||||
|
type AccountCredentials struct {
|
||||||
|
PassphraseSalt []byte
|
||||||
|
PassphraseHash []byte
|
||||||
|
Certificate string // fingerprint
|
||||||
|
}
|
||||||
|
|
||||||
// ClientAccount represents a user account.
|
// ClientAccount represents a user account.
|
||||||
type ClientAccount struct {
|
type ClientAccount struct {
|
||||||
// Name of the account.
|
// Name of the account.
|
||||||
Name string
|
Name string
|
||||||
// RegisteredAt represents the time that the account was registered.
|
// RegisteredAt represents the time that the account was registered.
|
||||||
RegisteredAt time.Time
|
RegisteredAt time.Time
|
||||||
// Clients that are currently logged into this account (useful for notifications).
|
Credentials AccountCredentials
|
||||||
Clients []*Client
|
Verified bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadAccountCredentials loads an account's credentials from the store.
|
// convenience for passing around raw serialized account data
|
||||||
func loadAccountCredentials(tx *buntdb.Tx, accountKey string) (*AccountCredentials, error) {
|
type rawClientAccount struct {
|
||||||
credText, err := tx.Get(fmt.Sprintf(keyAccountCredentials, accountKey))
|
Name string
|
||||||
if err != nil {
|
RegisteredAt string
|
||||||
return nil, err
|
Credentials string
|
||||||
}
|
Verified bool
|
||||||
|
|
||||||
var creds AccountCredentials
|
|
||||||
err = json.Unmarshal([]byte(credText), &creds)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &creds, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadAccount loads an account from the store, note that the account must actually exist.
|
|
||||||
func loadAccount(server *Server, tx *buntdb.Tx, accountKey string) *ClientAccount {
|
|
||||||
name, _ := tx.Get(fmt.Sprintf(keyAccountName, accountKey))
|
|
||||||
regTime, _ := tx.Get(fmt.Sprintf(keyAccountRegTime, accountKey))
|
|
||||||
regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
|
|
||||||
accountInfo := ClientAccount{
|
|
||||||
Name: name,
|
|
||||||
RegisteredAt: time.Unix(regTimeInt, 0),
|
|
||||||
Clients: []*Client{},
|
|
||||||
}
|
|
||||||
server.accounts[accountKey] = &accountInfo
|
|
||||||
|
|
||||||
return &accountInfo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginToAccount logs the client into the given account.
|
// LoginToAccount logs the client into the given account.
|
||||||
func (client *Client) LoginToAccount(account *ClientAccount) {
|
func (client *Client) LoginToAccount(account string) {
|
||||||
if client.account == account {
|
casefoldedAccount, err := CasefoldName(account)
|
||||||
// already logged into this acct, no changing necessary
|
if err != nil {
|
||||||
return
|
return
|
||||||
} else if client.LoggedIntoAccount() {
|
|
||||||
// logout of existing acct
|
|
||||||
var newClientAccounts []*Client
|
|
||||||
for _, c := range account.Clients {
|
|
||||||
if c != client {
|
|
||||||
newClientAccounts = append(newClientAccounts, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
account.Clients = newClientAccounts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
account.Clients = append(account.Clients, client)
|
if client.Account() == casefoldedAccount {
|
||||||
client.account = account
|
// already logged into this acct, no changing necessary
|
||||||
client.server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] logged into account $c[grey][$r%s$c[grey]]"), client.nickMaskString, account.Name))
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client.SetAccountName(casefoldedAccount)
|
||||||
|
client.nickTimer.Touch()
|
||||||
|
|
||||||
|
client.server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] logged into account $c[grey][$r%s$c[grey]]"), client.nickMaskString, casefoldedAccount))
|
||||||
|
|
||||||
//TODO(dan): This should output the AccountNotify message instead of the sasl accepted function below.
|
//TODO(dan): This should output the AccountNotify message instead of the sasl accepted function below.
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogoutOfAccount logs the client out of their current account.
|
// LogoutOfAccount logs the client out of their current account.
|
||||||
func (client *Client) LogoutOfAccount() {
|
func (client *Client) LogoutOfAccount() {
|
||||||
account := client.account
|
if client.Account() == "" {
|
||||||
if account == nil {
|
|
||||||
// already logged out
|
// already logged out
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// logout of existing acct
|
client.SetAccountName("")
|
||||||
var newClientAccounts []*Client
|
client.nickTimer.Touch()
|
||||||
for _, c := range account.Clients {
|
|
||||||
if c != client {
|
|
||||||
newClientAccounts = append(newClientAccounts, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
account.Clients = newClientAccounts
|
|
||||||
|
|
||||||
client.account = nil
|
|
||||||
|
|
||||||
// dispatch account-notify
|
// dispatch account-notify
|
||||||
for friend := range client.Friends(caps.AccountNotify) {
|
for friend := range client.Friends(caps.AccountNotify) {
|
||||||
@ -129,11 +516,11 @@ func (client *Client) LogoutOfAccount() {
|
|||||||
|
|
||||||
// successfulSaslAuth means that a SASL auth attempt completed successfully, and is used to dispatch messages.
|
// successfulSaslAuth means that a SASL auth attempt completed successfully, and is used to dispatch messages.
|
||||||
func (client *Client) successfulSaslAuth(rb *ResponseBuffer) {
|
func (client *Client) successfulSaslAuth(rb *ResponseBuffer) {
|
||||||
rb.Add(nil, client.server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, client.account.Name, fmt.Sprintf("You are now logged in as %s", client.account.Name))
|
rb.Add(nil, client.server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, client.AccountName(), fmt.Sprintf("You are now logged in as %s", client.AccountName()))
|
||||||
rb.Add(nil, client.server.name, RPL_SASLSUCCESS, client.nick, client.t("SASL authentication successful"))
|
rb.Add(nil, client.server.name, RPL_SASLSUCCESS, client.nick, client.t("SASL authentication successful"))
|
||||||
|
|
||||||
// dispatch account-notify
|
// dispatch account-notify
|
||||||
for friend := range client.Friends(caps.AccountNotify) {
|
for friend := range client.Friends(caps.AccountNotify) {
|
||||||
friend.Send(nil, client.nickMaskString, "ACCOUNT", client.account.Name)
|
friend.Send(nil, client.nickMaskString, "ACCOUNT", client.AccountName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -383,13 +383,13 @@ func (channel *Channel) Join(client *Client, key string, rb *ResponseBuffer) {
|
|||||||
for _, member := range channel.Members() {
|
for _, member := range channel.Members() {
|
||||||
if member == client {
|
if member == client {
|
||||||
if member.capabilities.Has(caps.ExtendedJoin) {
|
if member.capabilities.Has(caps.ExtendedJoin) {
|
||||||
rb.Add(nil, client.nickMaskString, "JOIN", channel.name, client.account.Name, client.realname)
|
rb.Add(nil, client.nickMaskString, "JOIN", channel.name, client.AccountName(), client.realname)
|
||||||
} else {
|
} else {
|
||||||
rb.Add(nil, client.nickMaskString, "JOIN", channel.name)
|
rb.Add(nil, client.nickMaskString, "JOIN", channel.name)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if member.capabilities.Has(caps.ExtendedJoin) {
|
if member.capabilities.Has(caps.ExtendedJoin) {
|
||||||
member.Send(nil, client.nickMaskString, "JOIN", channel.name, client.account.Name, client.realname)
|
member.Send(nil, client.nickMaskString, "JOIN", channel.name, client.AccountName(), client.realname)
|
||||||
} else {
|
} else {
|
||||||
member.Send(nil, client.nickMaskString, "JOIN", channel.name)
|
member.Send(nil, client.nickMaskString, "JOIN", channel.name)
|
||||||
}
|
}
|
||||||
@ -407,7 +407,9 @@ func (channel *Channel) Join(client *Client, key string, rb *ResponseBuffer) {
|
|||||||
// give channel mode if necessary
|
// give channel mode if necessary
|
||||||
newChannel := firstJoin && !channel.IsRegistered()
|
newChannel := firstJoin && !channel.IsRegistered()
|
||||||
var givenMode *modes.Mode
|
var givenMode *modes.Mode
|
||||||
if client.AccountName() == channel.registeredFounder {
|
account := client.Account()
|
||||||
|
cffounder, _ := CasefoldName(channel.registeredFounder)
|
||||||
|
if account != "" && account == cffounder {
|
||||||
givenMode = &modes.ChannelFounder
|
givenMode = &modes.ChannelFounder
|
||||||
} else if newChannel {
|
} else if newChannel {
|
||||||
givenMode = &modes.ChannelOperator
|
givenMode = &modes.ChannelOperator
|
||||||
@ -419,7 +421,7 @@ func (channel *Channel) Join(client *Client, key string, rb *ResponseBuffer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if client.capabilities.Has(caps.ExtendedJoin) {
|
if client.capabilities.Has(caps.ExtendedJoin) {
|
||||||
rb.Add(nil, client.nickMaskString, "JOIN", channel.name, client.account.Name, client.realname)
|
rb.Add(nil, client.nickMaskString, "JOIN", channel.name, client.AccountName(), client.realname)
|
||||||
} else {
|
} else {
|
||||||
rb.Add(nil, client.nickMaskString, "JOIN", channel.name)
|
rb.Add(nil, client.nickMaskString, "JOIN", channel.name)
|
||||||
}
|
}
|
||||||
@ -526,7 +528,7 @@ func (channel *Channel) CanSpeak(client *Client) bool {
|
|||||||
if channel.flags[modes.Moderated] && !channel.ClientIsAtLeast(client, modes.Voice) {
|
if channel.flags[modes.Moderated] && !channel.ClientIsAtLeast(client, modes.Voice) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if channel.flags[modes.RegisteredOnly] && client.account == &NoAccount {
|
if channel.flags[modes.RegisteredOnly] && client.Account() == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -70,13 +70,13 @@ func (server *Server) chanservRegisterHandler(client *Client, channelName string
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.account == &NoAccount {
|
if client.Account() == "" {
|
||||||
rb.ChanServNotice(client.t("You must be logged in to register a channel"))
|
rb.ChanServNotice(client.t("You must be logged in to register a channel"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// this provides the synchronization that allows exactly one registration of the channel:
|
// this provides the synchronization that allows exactly one registration of the channel:
|
||||||
err = channelInfo.SetRegistered(client.AccountName())
|
err = channelInfo.SetRegistered(client.Account())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rb.ChanServNotice(err.Error())
|
rb.ChanServNotice(err.Error())
|
||||||
return
|
return
|
||||||
|
@ -36,7 +36,8 @@ var (
|
|||||||
|
|
||||||
// Client is an IRC client.
|
// Client is an IRC client.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
account *ClientAccount
|
account string
|
||||||
|
accountName string
|
||||||
atime time.Time
|
atime time.Time
|
||||||
authorized bool
|
authorized bool
|
||||||
awayMessage string
|
awayMessage string
|
||||||
@ -62,6 +63,7 @@ type Client struct {
|
|||||||
nickCasefolded string
|
nickCasefolded string
|
||||||
nickMaskCasefolded string
|
nickMaskCasefolded string
|
||||||
nickMaskString string // cache for nickmask string since it's used with lots of replies
|
nickMaskString string // cache for nickmask string since it's used with lots of replies
|
||||||
|
nickTimer *NickTimer
|
||||||
operName string
|
operName string
|
||||||
proxiedIP net.IP // actual remote IP if using the PROXY protocol
|
proxiedIP net.IP // actual remote IP if using the PROXY protocol
|
||||||
quitMessage string
|
quitMessage string
|
||||||
@ -96,7 +98,6 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client {
|
|||||||
flags: make(map[modes.Mode]bool),
|
flags: make(map[modes.Mode]bool),
|
||||||
server: server,
|
server: server,
|
||||||
socket: &socket,
|
socket: &socket,
|
||||||
account: &NoAccount,
|
|
||||||
nick: "*", // * is used until actual nick is given
|
nick: "*", // * is used until actual nick is given
|
||||||
nickCasefolded: "*",
|
nickCasefolded: "*",
|
||||||
nickMaskString: "*", // * is used until actual nick is given
|
nickMaskString: "*", // * is used until actual nick is given
|
||||||
@ -217,6 +218,8 @@ func (client *Client) run() {
|
|||||||
client.idletimer = NewIdleTimer(client)
|
client.idletimer = NewIdleTimer(client)
|
||||||
client.idletimer.Start()
|
client.idletimer.Start()
|
||||||
|
|
||||||
|
client.nickTimer = NewNickTimer(client)
|
||||||
|
|
||||||
// Set the hostname for this client
|
// Set the hostname for this client
|
||||||
// (may be overridden by a later PROXY command from stunnel)
|
// (may be overridden by a later PROXY command from stunnel)
|
||||||
client.rawHostname = utils.AddrLookupHostname(client.socket.conn.RemoteAddr())
|
client.rawHostname = utils.AddrLookupHostname(client.socket.conn.RemoteAddr())
|
||||||
@ -299,7 +302,6 @@ func (client *Client) Register() {
|
|||||||
client.TryResume()
|
client.TryResume()
|
||||||
|
|
||||||
// finish registration
|
// finish registration
|
||||||
client.Touch()
|
|
||||||
client.updateNickMask("")
|
client.updateNickMask("")
|
||||||
client.server.monitorManager.AlertAbout(client, true)
|
client.server.monitorManager.AlertAbout(client, true)
|
||||||
}
|
}
|
||||||
@ -338,8 +340,8 @@ func (client *Client) TryResume() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
oldAccountName := oldClient.AccountName()
|
oldAccountName := oldClient.Account()
|
||||||
newAccountName := client.AccountName()
|
newAccountName := client.Account()
|
||||||
|
|
||||||
if oldAccountName == "" || newAccountName == "" || oldAccountName != newAccountName {
|
if oldAccountName == "" || newAccountName == "" || oldAccountName != newAccountName {
|
||||||
client.Send(nil, server.name, ERR_CANNOT_RESUME, oldnick, client.t("Cannot resume connection, old and new clients must be logged into the same account"))
|
client.Send(nil, server.name, ERR_CANNOT_RESUME, oldnick, client.t("Cannot resume connection, old and new clients must be logged into the same account"))
|
||||||
@ -406,7 +408,7 @@ func (client *Client) TryResume() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if member.capabilities.Has(caps.ExtendedJoin) {
|
if member.capabilities.Has(caps.ExtendedJoin) {
|
||||||
member.Send(nil, client.nickMaskString, "JOIN", channel.name, client.account.Name, client.realname)
|
member.Send(nil, client.nickMaskString, "JOIN", channel.name, client.AccountName(), client.realname)
|
||||||
} else {
|
} else {
|
||||||
member.Send(nil, client.nickMaskString, "JOIN", channel.name)
|
member.Send(nil, client.nickMaskString, "JOIN", channel.name)
|
||||||
}
|
}
|
||||||
@ -589,7 +591,7 @@ func (client *Client) AllNickmasks() []string {
|
|||||||
|
|
||||||
// LoggedIntoAccount returns true if this client is logged into an account.
|
// LoggedIntoAccount returns true if this client is logged into an account.
|
||||||
func (client *Client) LoggedIntoAccount() bool {
|
func (client *Client) LoggedIntoAccount() bool {
|
||||||
return client.account != nil && client.account != &NoAccount
|
return client.Account() != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// RplISupport outputs our ISUPPORT lines to the client. This is used on connection and in VERSION responses.
|
// RplISupport outputs our ISUPPORT lines to the client. This is used on connection and in VERSION responses.
|
||||||
@ -687,6 +689,8 @@ func (client *Client) destroy(beingResumed bool) {
|
|||||||
client.idletimer.Stop()
|
client.idletimer.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client.server.accounts.Logout(client)
|
||||||
|
|
||||||
client.socket.Close()
|
client.socket.Close()
|
||||||
|
|
||||||
// send quit messages to friends
|
// send quit messages to friends
|
||||||
@ -723,11 +727,11 @@ func (client *Client) SendSplitMsgFromClient(msgid string, from *Client, tags *m
|
|||||||
// Adds account-tag to the line as well.
|
// Adds account-tag to the line as well.
|
||||||
func (client *Client) SendFromClient(msgid string, from *Client, tags *map[string]ircmsg.TagValue, command string, params ...string) error {
|
func (client *Client) SendFromClient(msgid string, from *Client, tags *map[string]ircmsg.TagValue, command string, params ...string) error {
|
||||||
// attach account-tag
|
// attach account-tag
|
||||||
if client.capabilities.Has(caps.AccountTag) && from.account != &NoAccount {
|
if client.capabilities.Has(caps.AccountTag) && client.LoggedIntoAccount() {
|
||||||
if tags == nil {
|
if tags == nil {
|
||||||
tags = ircmsg.MakeTags("account", from.account.Name)
|
tags = ircmsg.MakeTags("account", from.AccountName())
|
||||||
} else {
|
} else {
|
||||||
(*tags)["account"] = ircmsg.MakeTagValue(from.account.Name)
|
(*tags)["account"] = ircmsg.MakeTagValue(from.AccountName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// attach message-id
|
// attach message-id
|
||||||
@ -772,10 +776,8 @@ func (client *Client) SendRawMessage(message ircmsg.IrcMessage) error {
|
|||||||
maxlenTags, maxlenRest := client.maxlens()
|
maxlenTags, maxlenRest := client.maxlens()
|
||||||
line, err := message.LineMaxLen(maxlenTags, maxlenRest)
|
line, err := message.LineMaxLen(maxlenTags, maxlenRest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// try not to fail quietly - especially useful when running tests, as a note to dig deeper
|
logline := fmt.Sprintf("Error assembling message for sending: %v\n%s", err, debug.Stack())
|
||||||
// log.Println("Error assembling message:")
|
client.server.logger.Error("internal", logline)
|
||||||
// spew.Dump(message)
|
|
||||||
// debug.PrintStack()
|
|
||||||
|
|
||||||
message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
|
message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
|
||||||
line, _ := message.Line()
|
line, _ := message.Line()
|
||||||
|
@ -98,6 +98,12 @@ func (clients *ClientManager) SetNick(client *Client, newNick string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var reservedAccount string
|
||||||
|
reservation := client.server.AccountConfig().NickReservation
|
||||||
|
if reservation != NickReservationDisabled {
|
||||||
|
reservedAccount = client.server.accounts.NickToAccount(newcfnick)
|
||||||
|
}
|
||||||
|
|
||||||
clients.Lock()
|
clients.Lock()
|
||||||
defer clients.Unlock()
|
defer clients.Unlock()
|
||||||
|
|
||||||
@ -107,6 +113,9 @@ func (clients *ClientManager) SetNick(client *Client, newNick string) error {
|
|||||||
if currentNewEntry != nil && currentNewEntry != client {
|
if currentNewEntry != nil && currentNewEntry != client {
|
||||||
return errNicknameInUse
|
return errNicknameInUse
|
||||||
}
|
}
|
||||||
|
if reservation == NickReservationStrict && reservedAccount != client.Account() {
|
||||||
|
return errNicknameReserved
|
||||||
|
}
|
||||||
clients.byNick[newcfnick] = client
|
clients.byNick[newcfnick] = client
|
||||||
client.updateNickMask(newNick)
|
client.updateNickMask(newNick)
|
||||||
return nil
|
return nil
|
||||||
|
@ -39,12 +39,7 @@ func (cmd *Command) Run(server *Server, client *Client, msg ircmsg.IrcMessage) b
|
|||||||
client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, client.t("Not enough parameters"))
|
client.Send(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, client.t("Not enough parameters"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !cmd.leaveClientActive {
|
|
||||||
client.Active()
|
|
||||||
}
|
|
||||||
if !cmd.leaveClientIdle {
|
|
||||||
client.Touch()
|
|
||||||
}
|
|
||||||
rb := NewResponseBuffer(client)
|
rb := NewResponseBuffer(client)
|
||||||
rb.Label = GetLabel(msg)
|
rb.Label = GetLabel(msg)
|
||||||
|
|
||||||
@ -57,6 +52,14 @@ func (cmd *Command) Run(server *Server, client *Client, msg ircmsg.IrcMessage) b
|
|||||||
server.tryRegister(client)
|
server.tryRegister(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !cmd.leaveClientIdle {
|
||||||
|
client.Touch()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cmd.leaveClientActive {
|
||||||
|
client.Active()
|
||||||
|
}
|
||||||
|
|
||||||
return exiting
|
return exiting
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +70,7 @@ func init() {
|
|||||||
Commands = map[string]Command{
|
Commands = map[string]Command{
|
||||||
"ACC": {
|
"ACC": {
|
||||||
handler: accHandler,
|
handler: accHandler,
|
||||||
minParams: 3,
|
minParams: 2,
|
||||||
},
|
},
|
||||||
"AMBIANCE": {
|
"AMBIANCE": {
|
||||||
handler: sceneHandler,
|
handler: sceneHandler,
|
||||||
@ -98,6 +101,7 @@ func init() {
|
|||||||
"DEBUG": {
|
"DEBUG": {
|
||||||
handler: debugHandler,
|
handler: debugHandler,
|
||||||
minParams: 1,
|
minParams: 1,
|
||||||
|
oper: true,
|
||||||
},
|
},
|
||||||
"DLINE": {
|
"DLINE": {
|
||||||
handler: dlineHandler,
|
handler: dlineHandler,
|
||||||
|
@ -8,6 +8,7 @@ package irc
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
@ -57,11 +58,49 @@ func (conf *PassConfig) PasswordBytes() []byte {
|
|||||||
return bytes
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
// AccountRegistrationConfig controls account registration.
|
// AccountRegistrationConfig controls account registration.
|
||||||
type AccountRegistrationConfig struct {
|
type AccountRegistrationConfig struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
EnabledCallbacks []string `yaml:"enabled-callbacks"`
|
EnabledCallbacks []string `yaml:"enabled-callbacks"`
|
||||||
Callbacks struct {
|
EnabledCredentialTypes []string `yaml:"-"`
|
||||||
|
VerifyTimeout time.Duration `yaml:"verify-timeout"`
|
||||||
|
Callbacks struct {
|
||||||
Mailto struct {
|
Mailto struct {
|
||||||
Server string
|
Server string
|
||||||
Port int
|
Port int
|
||||||
@ -180,10 +219,7 @@ type Config struct {
|
|||||||
Path string
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
Accounts struct {
|
Accounts AccountConfig
|
||||||
Registration AccountRegistrationConfig
|
|
||||||
AuthenticationEnabled bool `yaml:"authentication-enabled"`
|
|
||||||
}
|
|
||||||
|
|
||||||
Channels struct {
|
Channels struct {
|
||||||
DefaultModes *string `yaml:"default-modes"`
|
DefaultModes *string `yaml:"default-modes"`
|
||||||
@ -469,6 +505,15 @@ func LoadConfig(filename string) (config *Config, err error) {
|
|||||||
}
|
}
|
||||||
config.Logging = newLogConfigs
|
config.Logging = newLogConfigs
|
||||||
|
|
||||||
|
// hardcode this for now
|
||||||
|
config.Accounts.Registration.EnabledCredentialTypes = []string{"passphrase", "certfp"}
|
||||||
|
for i, name := range config.Accounts.Registration.EnabledCallbacks {
|
||||||
|
if name == "none" {
|
||||||
|
// we store "none" as "*" internally
|
||||||
|
config.Accounts.Registration.EnabledCallbacks[i] = "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
config.Server.MaxSendQBytes, err = bytefmt.ToBytes(config.Server.MaxSendQString)
|
config.Server.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())
|
||||||
|
@ -9,18 +9,25 @@ import "errors"
|
|||||||
|
|
||||||
// Runtime Errors
|
// Runtime Errors
|
||||||
var (
|
var (
|
||||||
errAccountCreation = errors.New("Account could not be created")
|
errAccountAlreadyRegistered = errors.New("Account already exists")
|
||||||
errCertfpAlreadyExists = errors.New("An account already exists with your certificate")
|
errAccountCreation = errors.New("Account could not be created")
|
||||||
errChannelAlreadyRegistered = errors.New("Channel is already registered")
|
errAccountDoesNotExist = errors.New("Account does not exist")
|
||||||
errChannelNameInUse = errors.New("Channel name in use")
|
errAccountVerificationFailed = errors.New("Account verification failed")
|
||||||
errInvalidChannelName = errors.New("Invalid channel name")
|
errAccountUnverified = errors.New("Account is not yet verified")
|
||||||
errMonitorLimitExceeded = errors.New("Monitor limit exceeded")
|
errAccountAlreadyVerified = errors.New("Account is already verified")
|
||||||
errNickMissing = errors.New("nick missing")
|
errAccountInvalidCredentials = errors.New("Invalid account credentials")
|
||||||
errNicknameInUse = errors.New("nickname in use")
|
errCertfpAlreadyExists = errors.New("An account already exists with your certificate")
|
||||||
errNoExistingBan = errors.New("Ban does not exist")
|
errChannelAlreadyRegistered = errors.New("Channel is already registered")
|
||||||
errNoSuchChannel = errors.New("No such channel")
|
errChannelNameInUse = errors.New("Channel name in use")
|
||||||
errRenamePrivsNeeded = errors.New("Only chanops can rename channels")
|
errInvalidChannelName = errors.New("Invalid channel name")
|
||||||
errSaslFail = errors.New("SASL failed")
|
errMonitorLimitExceeded = errors.New("Monitor limit exceeded")
|
||||||
|
errNickMissing = errors.New("nick missing")
|
||||||
|
errNicknameInUse = errors.New("nickname in use")
|
||||||
|
errNicknameReserved = errors.New("nickname is reserved")
|
||||||
|
errNoExistingBan = errors.New("Ban does not exist")
|
||||||
|
errNoSuchChannel = errors.New("No such channel")
|
||||||
|
errRenamePrivsNeeded = errors.New("Only chanops can rename channels")
|
||||||
|
errSaslFail = errors.New("SASL failed")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Socket Errors
|
// Socket Errors
|
||||||
|
@ -56,6 +56,12 @@ func (server *Server) ChannelRegistrationEnabled() bool {
|
|||||||
return server.channelRegistrationEnabled
|
return server.channelRegistrationEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (server *Server) AccountConfig() *AccountConfig {
|
||||||
|
server.configurableStateMutex.RLock()
|
||||||
|
defer server.configurableStateMutex.RUnlock()
|
||||||
|
return server.accountConfig
|
||||||
|
}
|
||||||
|
|
||||||
func (client *Client) Nick() string {
|
func (client *Client) Nick() string {
|
||||||
client.stateMutex.RLock()
|
client.stateMutex.RLock()
|
||||||
defer client.stateMutex.RUnlock()
|
defer client.stateMutex.RUnlock()
|
||||||
@ -104,10 +110,30 @@ func (client *Client) Destroyed() bool {
|
|||||||
return client.isDestroyed
|
return client.isDestroyed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (client *Client) Account() string {
|
||||||
|
client.stateMutex.RLock()
|
||||||
|
defer client.stateMutex.RUnlock()
|
||||||
|
return client.account
|
||||||
|
}
|
||||||
|
|
||||||
func (client *Client) AccountName() string {
|
func (client *Client) AccountName() string {
|
||||||
client.stateMutex.RLock()
|
client.stateMutex.RLock()
|
||||||
defer client.stateMutex.RUnlock()
|
defer client.stateMutex.RUnlock()
|
||||||
return client.account.Name
|
if client.accountName == "" {
|
||||||
|
return "*"
|
||||||
|
}
|
||||||
|
return client.accountName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *Client) SetAccountName(account string) {
|
||||||
|
var casefoldedAccount string
|
||||||
|
if account != "" {
|
||||||
|
casefoldedAccount, _ = CasefoldName(account)
|
||||||
|
}
|
||||||
|
client.stateMutex.Lock()
|
||||||
|
defer client.stateMutex.Unlock()
|
||||||
|
client.account = casefoldedAccount
|
||||||
|
client.accountName = account
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) HasMode(mode modes.Mode) bool {
|
func (client *Client) HasMode(mode modes.Mode) bool {
|
||||||
|
280
irc/handlers.go
280
irc/handlers.go
@ -11,7 +11,6 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -42,6 +41,8 @@ func accHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
|
|||||||
return accRegisterHandler(server, client, msg, rb)
|
return accRegisterHandler(server, client, msg, rb)
|
||||||
} else if subcommand == "verify" {
|
} else if subcommand == "verify" {
|
||||||
rb.Notice(client.t("VERIFY is not yet implemented"))
|
rb.Notice(client.t("VERIFY is not yet implemented"))
|
||||||
|
} else if subcommand == "unregister" {
|
||||||
|
return accUnregisterHandler(server, client, msg, rb)
|
||||||
} else {
|
} else {
|
||||||
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "ACC", msg.Params[0], client.t("Unknown subcommand"))
|
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "ACC", msg.Params[0], client.t("Unknown subcommand"))
|
||||||
}
|
}
|
||||||
@ -49,18 +50,45 @@ func accHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ACC UNREGISTER <accountname>
|
||||||
|
func accUnregisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
|
// get and sanitise account name
|
||||||
|
account := strings.TrimSpace(msg.Params[1])
|
||||||
|
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 || msg.Params[1] == "*" {
|
||||||
|
rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, client.nick, account, client.t("Account name is not valid"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(account == client.Account() || client.HasRoleCapabs("unregister")) {
|
||||||
|
rb.Add(nil, server.name, ERR_NOPRIVS, client.Nick(), account, client.t("Insufficient oper privs"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
err = server.accounts.Unregister(account)
|
||||||
|
// TODO better responses all around here
|
||||||
|
if err != nil {
|
||||||
|
errorMsg := fmt.Sprintf("Unknown error while unregistering account %s", casefoldedAccount)
|
||||||
|
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), msg.Command, errorMsg)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
rb.Notice(fmt.Sprintf("Successfully unregistered account %s", casefoldedAccount))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
// make sure reg is enabled
|
// make sure reg is enabled
|
||||||
if !server.accountRegistration.Enabled {
|
if !server.AccountConfig().Registration.Enabled {
|
||||||
rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, client.nick, "*", client.t("Account registration is disabled"))
|
rb.Add(nil, server.name, ERR_REG_UNSPECIFIED_ERROR, client.nick, "*", client.t("Account registration is disabled"))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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() {
|
||||||
if server.accountRegistration.AllowMultiplePerConnection {
|
if server.AccountConfig().Registration.AllowMultiplePerConnection {
|
||||||
client.LogoutOfAccount()
|
server.accounts.Logout(client)
|
||||||
} else {
|
} else {
|
||||||
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, client.nick, "*", client.t("You're already logged into an account"))
|
||||||
return false
|
return false
|
||||||
@ -76,36 +104,11 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// check whether account exists
|
if len(msg.Params) < 4 {
|
||||||
// do it all in one write tx to prevent races
|
rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, client.t("Not enough parameters"))
|
||||||
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
|
|
||||||
rb.Add(nil, server.name, ERR_ACCOUNT_ALREADY_EXISTS, client.nick, account, 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 {
|
|
||||||
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "ACC", "REGISTER", client.t("Could not register"))
|
|
||||||
log.Println("Could not save registration initial data:", err.Error())
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// account didn't already exist, continue with account creation and dispatching verification (if required)
|
|
||||||
callback := strings.ToLower(msg.Params[2])
|
callback := strings.ToLower(msg.Params[2])
|
||||||
var callbackNamespace, callbackValue string
|
var callbackNamespace, callbackValue string
|
||||||
|
|
||||||
@ -115,14 +118,14 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r
|
|||||||
callbackValues := strings.SplitN(callback, ":", 2)
|
callbackValues := strings.SplitN(callback, ":", 2)
|
||||||
callbackNamespace, callbackValue = callbackValues[0], callbackValues[1]
|
callbackNamespace, callbackValue = callbackValues[0], callbackValues[1]
|
||||||
} else {
|
} else {
|
||||||
callbackNamespace = server.accountRegistration.EnabledCallbacks[0]
|
callbackNamespace = server.AccountConfig().Registration.EnabledCallbacks[0]
|
||||||
callbackValue = callback
|
callbackValue = callback
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure the callback namespace is valid
|
// ensure the callback namespace is valid
|
||||||
// need to search callback list, maybe look at using a map later?
|
// need to search callback list, maybe look at using a map later?
|
||||||
var callbackValid bool
|
var callbackValid bool
|
||||||
for _, name := range server.accountRegistration.EnabledCallbacks {
|
for _, name := range server.AccountConfig().Registration.EnabledCallbacks {
|
||||||
if callbackNamespace == name {
|
if callbackNamespace == name {
|
||||||
callbackValid = true
|
callbackValid = true
|
||||||
}
|
}
|
||||||
@ -130,7 +133,6 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r
|
|||||||
|
|
||||||
if !callbackValid {
|
if !callbackValid {
|
||||||
rb.Add(nil, server.name, ERR_REG_INVALID_CALLBACK, client.nick, account, callbackNamespace, client.t("Callback namespace is not supported"))
|
rb.Add(nil, server.name, ERR_REG_INVALID_CALLBACK, client.nick, account, callbackNamespace, client.t("Callback namespace is not supported"))
|
||||||
removeFailedAccRegisterData(server.store, casefoldedAccount)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,116 +142,62 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r
|
|||||||
if len(msg.Params) > 4 {
|
if len(msg.Params) > 4 {
|
||||||
credentialType = strings.ToLower(msg.Params[3])
|
credentialType = strings.ToLower(msg.Params[3])
|
||||||
credentialValue = msg.Params[4]
|
credentialValue = msg.Params[4]
|
||||||
} else if len(msg.Params) == 4 {
|
} else {
|
||||||
|
// exactly 4 params
|
||||||
credentialType = "passphrase" // default from the spec
|
credentialType = "passphrase" // default from the spec
|
||||||
credentialValue = msg.Params[3]
|
credentialValue = msg.Params[3]
|
||||||
} else {
|
|
||||||
rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.nick, msg.Command, client.t("Not enough parameters"))
|
|
||||||
removeFailedAccRegisterData(server.store, casefoldedAccount)
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure the credential type is valid
|
// ensure the credential type is valid
|
||||||
var credentialValid bool
|
var credentialValid bool
|
||||||
for _, name := range server.accountRegistration.EnabledCredentialTypes {
|
for _, name := range server.AccountConfig().Registration.EnabledCredentialTypes {
|
||||||
if credentialType == name {
|
if credentialType == name {
|
||||||
credentialValid = true
|
credentialValid = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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, client.nick, credentialType, callbackNamespace, client.t("You are not using a TLS certificate"))
|
||||||
removeFailedAccRegisterData(server.store, casefoldedAccount)
|
|
||||||
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, client.nick, credentialType, callbackNamespace, client.t("Credential type is not supported"))
|
||||||
removeFailedAccRegisterData(server.store, casefoldedAccount)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// store details
|
var passphrase, certfp string
|
||||||
err = server.store.Update(func(tx *buntdb.Tx) error {
|
if credentialType == "certfp" {
|
||||||
// certfp special lookup key
|
certfp = client.certfp
|
||||||
if credentialType == "certfp" {
|
} else if credentialType == "passphrase" {
|
||||||
assembledKeyCertToAccount := fmt.Sprintf(keyCertToAccount, client.certfp)
|
passphrase = credentialValue
|
||||||
|
}
|
||||||
// make sure certfp doesn't already exist because that'd be silly
|
err = server.accounts.Register(client, account, callbackNamespace, callbackValue, passphrase, certfp)
|
||||||
_, 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 credentialType == "certfp" {
|
|
||||||
creds.Certificate = client.certfp
|
|
||||||
} else if credentialType == "passphrase" {
|
|
||||||
creds.PassphraseHash, err = server.passwords.GenerateFromPassword(creds.PassphraseSalt, credentialValue)
|
|
||||||
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 {
|
if err != nil {
|
||||||
errMsg := "Could not register"
|
msg := "Unknown"
|
||||||
|
code := ERR_UNKNOWNERROR
|
||||||
if err == errCertfpAlreadyExists {
|
if err == errCertfpAlreadyExists {
|
||||||
errMsg = "An account already exists for your certificate fingerprint"
|
msg = "An account already exists for your certificate fingerprint"
|
||||||
|
} else if err == errAccountAlreadyRegistered {
|
||||||
|
msg = "Account already exists"
|
||||||
|
code = ERR_ACCOUNT_ALREADY_EXISTS
|
||||||
}
|
}
|
||||||
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "ACC", "REGISTER", errMsg)
|
if err == errAccountAlreadyRegistered || err == errAccountCreation || err == errCertfpAlreadyExists {
|
||||||
log.Println("Could not save registration creds:", err.Error())
|
msg = err.Error()
|
||||||
removeFailedAccRegisterData(server.store, casefoldedAccount)
|
}
|
||||||
|
rb.Add(nil, server.name, code, client.nick, "ACC", "REGISTER", client.t(msg))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// automatically complete registration
|
// automatically complete registration
|
||||||
if callbackNamespace == "*" {
|
if callbackNamespace == "*" {
|
||||||
err = server.store.Update(func(tx *buntdb.Tx) error {
|
err := server.accounts.Verify(client, casefoldedAccount, "")
|
||||||
tx.Set(fmt.Sprintf(keyAccountVerified, casefoldedAccount), "1", nil)
|
|
||||||
|
|
||||||
// load acct info inside store tx
|
|
||||||
account := ClientAccount{
|
|
||||||
Name: strings.TrimSpace(msg.Params[1]),
|
|
||||||
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
|
|
||||||
|
|
||||||
rb.Add(nil, server.name, RPL_REGISTRATION_SUCCESS, client.nick, account.Name, client.t("Account created"))
|
|
||||||
rb.Add(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, account.Name, fmt.Sprintf(client.t("You are now logged in as %s"), account.Name))
|
|
||||||
rb.Add(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 {
|
if err != nil {
|
||||||
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "ACC", "REGISTER", client.t("Could not register"))
|
|
||||||
log.Println("Could not save verification confirmation (*):", err.Error())
|
|
||||||
removeFailedAccRegisterData(server.store, casefoldedAccount)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
client.Send(nil, server.name, RPL_REGISTRATION_SUCCESS, client.nick, casefoldedAccount, client.t("Account created"))
|
||||||
return false
|
client.Send(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, casefoldedAccount, fmt.Sprintf(client.t("You are now logged in as %s"), casefoldedAccount))
|
||||||
|
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]]"), casefoldedAccount, client.nickMaskString))
|
||||||
}
|
}
|
||||||
|
|
||||||
// dispatch callback
|
// dispatch callback
|
||||||
@ -261,7 +209,7 @@ func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r
|
|||||||
// AUTHENTICATE [<mechanism>|<data>|*]
|
// AUTHENTICATE [<mechanism>|<data>|*]
|
||||||
func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
// sasl abort
|
// sasl abort
|
||||||
if !server.accountAuthenticationEnabled || len(msg.Params) == 1 && msg.Params[0] == "*" {
|
if !server.AccountConfig().AuthenticationEnabled || len(msg.Params) == 1 && msg.Params[0] == "*" {
|
||||||
rb.Add(nil, server.name, ERR_SASLABORTED, client.nick, client.t("SASL authentication aborted"))
|
rb.Add(nil, server.name, ERR_SASLABORTED, client.nick, client.t("SASL authentication aborted"))
|
||||||
client.saslInProgress = false
|
client.saslInProgress = false
|
||||||
client.saslMechanism = ""
|
client.saslMechanism = ""
|
||||||
@ -374,40 +322,11 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value []
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// load and check acct data all in one update to prevent races.
|
password := string(splitValue[2])
|
||||||
// as noted elsewhere, change to proper locking for Account type later probably
|
err = server.accounts.AuthenticateByPassphrase(client, accountKey, password)
|
||||||
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
|
|
||||||
password := string(splitValue[2])
|
|
||||||
if len(creds.PassphraseHash) < 1 || len(creds.PassphraseSalt) < 1 || len(password) < 1 {
|
|
||||||
return errSaslFail
|
|
||||||
}
|
|
||||||
err = server.passwords.CompareHashAndPassword(creds.PassphraseHash, creds.PassphraseSalt, password)
|
|
||||||
|
|
||||||
// succeeded, load account info if necessary
|
|
||||||
account, exists := server.accounts[accountKey]
|
|
||||||
if !exists {
|
|
||||||
account = loadAccount(server, tx, accountKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
client.LoginToAccount(account)
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed"))
|
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)))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,6 +334,16 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value []
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func authErrorToMessage(server *Server, err error) (msg string) {
|
||||||
|
if err == errAccountDoesNotExist || err == errAccountUnverified || err == errAccountInvalidCredentials {
|
||||||
|
msg = err.Error()
|
||||||
|
} else {
|
||||||
|
server.logger.Error("internal", fmt.Sprintf("sasl authentication failure: %v", err))
|
||||||
|
msg = "Unknown"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// AUTHENTICATE EXTERNAL
|
// AUTHENTICATE EXTERNAL
|
||||||
func authExternalHandler(server *Server, client *Client, mechanism string, value []byte, rb *ResponseBuffer) bool {
|
func authExternalHandler(server *Server, client *Client, mechanism string, value []byte, rb *ResponseBuffer) bool {
|
||||||
if client.certfp == "" {
|
if client.certfp == "" {
|
||||||
@ -422,44 +351,10 @@ func authExternalHandler(server *Server, client *Client, mechanism string, value
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
err := server.store.Update(func(tx *buntdb.Tx) error {
|
err := server.accounts.AuthenticateByCertFP(client)
|
||||||
// certfp lookup key
|
|
||||||
accountKey, err := tx.Get(fmt.Sprintf(keyCertToAccount, client.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)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed"))
|
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)))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -582,11 +477,16 @@ func csHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respon
|
|||||||
|
|
||||||
// DEBUG <subcmd>
|
// DEBUG <subcmd>
|
||||||
func debugHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
func debugHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
|
||||||
if !client.flags[modes.Operator] {
|
param, err := Casefold(msg.Params[0])
|
||||||
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
switch msg.Params[0] {
|
if !client.HasMode(modes.Operator) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch param {
|
||||||
case "GCSTATS":
|
case "GCSTATS":
|
||||||
stats := debug.GCStats{
|
stats := debug.GCStats{
|
||||||
Pause: make([]time.Duration, 10),
|
Pause: make([]time.Duration, 10),
|
||||||
@ -2107,7 +2007,7 @@ func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
|||||||
}
|
}
|
||||||
|
|
||||||
founder := channel.Founder()
|
founder := channel.Founder()
|
||||||
if founder != "" && founder != client.AccountName() {
|
if founder != "" && founder != client.Account() {
|
||||||
//TODO(dan): Change this to ERR_CANNOTRENAME
|
//TODO(dan): Change this to ERR_CANNOTRENAME
|
||||||
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "RENAME", oldName, client.t("Only channel founders can change registered channels"))
|
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "RENAME", oldName, client.t("Only channel founders can change registered channels"))
|
||||||
return false
|
return false
|
||||||
@ -2130,11 +2030,7 @@ func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
|||||||
} else {
|
} else {
|
||||||
mcl.Send(nil, mcl.nickMaskString, "PART", oldName, fmt.Sprintf(mcl.t("Channel renamed: %s"), reason))
|
mcl.Send(nil, mcl.nickMaskString, "PART", oldName, fmt.Sprintf(mcl.t("Channel renamed: %s"), reason))
|
||||||
if mcl.capabilities.Has(caps.ExtendedJoin) {
|
if mcl.capabilities.Has(caps.ExtendedJoin) {
|
||||||
accountName := "*"
|
mcl.Send(nil, mcl.nickMaskString, "JOIN", newName, mcl.AccountName(), mcl.realname)
|
||||||
if mcl.account != nil {
|
|
||||||
accountName = mcl.account.Name
|
|
||||||
}
|
|
||||||
mcl.Send(nil, mcl.nickMaskString, "JOIN", newName, accountName, mcl.realname)
|
|
||||||
} else {
|
} else {
|
||||||
mcl.Send(nil, mcl.nickMaskString, "JOIN", newName)
|
mcl.Send(nil, mcl.nickMaskString, "JOIN", newName)
|
||||||
}
|
}
|
||||||
|
@ -165,3 +165,80 @@ func (it *IdleTimer) quitMessage(state TimerState) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NickTimer manages timing out of clients who are squatting reserved nicks
|
||||||
|
type NickTimer struct {
|
||||||
|
sync.Mutex // tier 1
|
||||||
|
|
||||||
|
// immutable after construction
|
||||||
|
timeout time.Duration
|
||||||
|
client *Client
|
||||||
|
|
||||||
|
// mutable
|
||||||
|
nick string
|
||||||
|
accountForNick string
|
||||||
|
account string
|
||||||
|
timer *time.Timer
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
nt := NickTimer{
|
||||||
|
client: client,
|
||||||
|
timeout: config.NickReservationTimeout,
|
||||||
|
}
|
||||||
|
return &nt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touch records a nick change and updates the timer as necessary
|
||||||
|
func (nt *NickTimer) Touch() {
|
||||||
|
if nt == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nick := nt.client.NickCasefolded()
|
||||||
|
account := nt.client.Account()
|
||||||
|
accountForNick := nt.client.server.accounts.NickToAccount(nick)
|
||||||
|
|
||||||
|
var shouldWarn bool
|
||||||
|
|
||||||
|
func() {
|
||||||
|
nt.Lock()
|
||||||
|
defer nt.Unlock()
|
||||||
|
// the timer will not reset as long as the squatter is targeting the same account
|
||||||
|
accountChanged := accountForNick != nt.accountForNick
|
||||||
|
// change state
|
||||||
|
nt.nick = nick
|
||||||
|
nt.account = account
|
||||||
|
nt.accountForNick = accountForNick
|
||||||
|
delinquent := accountForNick != "" && accountForNick != account
|
||||||
|
|
||||||
|
if nt.timer != nil && (!delinquent || accountChanged) {
|
||||||
|
nt.timer.Stop()
|
||||||
|
nt.timer = nil
|
||||||
|
}
|
||||||
|
if delinquent && accountChanged {
|
||||||
|
nt.timer = time.AfterFunc(nt.timeout, nt.processTimeout)
|
||||||
|
shouldWarn = true
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if shouldWarn {
|
||||||
|
nt.sendWarning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nt *NickTimer) sendWarning() {
|
||||||
|
baseNotice := "Nickname is reserved; you must change it or authenticate to NickServ within %v"
|
||||||
|
nt.client.Notice(fmt.Sprintf(nt.client.t(baseNotice), nt.timeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
@ -17,6 +17,7 @@ var (
|
|||||||
"=scene=": true, // used for rp commands
|
"=scene=": true, // used for rp commands
|
||||||
"chanserv": true,
|
"chanserv": true,
|
||||||
"nickserv": true,
|
"nickserv": true,
|
||||||
|
"hostserv": true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,11 +46,16 @@ func performNickChange(server *Server, client *Client, target *Client, newnick s
|
|||||||
if err == errNicknameInUse {
|
if err == errNicknameInUse {
|
||||||
rb.Add(nil, server.name, ERR_NICKNAMEINUSE, client.nick, nickname, client.t("Nickname is already in use"))
|
rb.Add(nil, server.name, ERR_NICKNAMEINUSE, client.nick, nickname, client.t("Nickname is already in use"))
|
||||||
return false
|
return false
|
||||||
|
} else if err == errNicknameReserved {
|
||||||
|
client.Send(nil, server.name, ERR_NICKNAMEINUSE, client.nick, nickname, client.t("Nickname is reserved by a different account"))
|
||||||
|
return false
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "NICK", fmt.Sprintf(client.t("Could not set or change nickname: %s"), err.Error()))
|
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "NICK", fmt.Sprintf(client.t("Could not set or change nickname: %s"), err.Error()))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client.nickTimer.Touch()
|
||||||
|
|
||||||
client.server.logger.Debug("nick", fmt.Sprintf("%s changed nickname to %s [%s]", origNickMask, nickname, cfnick))
|
client.server.logger.Debug("nick", fmt.Sprintf("%s changed nickname to %s [%s]", origNickMask, nickname, cfnick))
|
||||||
if hadNick {
|
if hadNick {
|
||||||
target.server.snomasks.Send(sno.LocalNicks, fmt.Sprintf(ircfmt.Unescape("$%s$r changed nickname to %s"), origNick, nickname))
|
target.server.snomasks.Send(sno.LocalNicks, fmt.Sprintf(ircfmt.Unescape("$%s$r changed nickname to %s"), origNick, nickname))
|
||||||
|
191
irc/nickserv.go
191
irc/nickserv.go
@ -4,16 +4,11 @@
|
|||||||
package irc
|
package irc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/goshuirc/irc-go/ircfmt"
|
"github.com/goshuirc/irc-go/ircfmt"
|
||||||
"github.com/oragono/oragono/irc/passwd"
|
|
||||||
"github.com/oragono/oragono/irc/sno"
|
"github.com/oragono/oragono/irc/sno"
|
||||||
"github.com/tidwall/buntdb"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const nickservHelp = `NickServ lets you register and log into a user account.
|
const nickservHelp = `NickServ lets you register and log into a user account.
|
||||||
@ -80,14 +75,14 @@ func (server *Server) nickservRegisterHandler(client *Client, username, passphra
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !server.accountRegistration.Enabled {
|
if !server.AccountConfig().Registration.Enabled {
|
||||||
rb.Notice(client.t("Account registration has been disabled"))
|
rb.Notice(client.t("Account registration has been disabled"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.LoggedIntoAccount() {
|
if client.LoggedIntoAccount() {
|
||||||
if server.accountRegistration.AllowMultiplePerConnection {
|
if server.AccountConfig().Registration.AllowMultiplePerConnection {
|
||||||
client.LogoutOfAccount()
|
server.accounts.Logout(client)
|
||||||
} else {
|
} else {
|
||||||
rb.Notice(client.t("You're already logged into an account"))
|
rb.Notice(client.t("You're already logged into an account"))
|
||||||
return
|
return
|
||||||
@ -103,26 +98,6 @@ func (server *Server) nickservRegisterHandler(client *Client, username, passphra
|
|||||||
return
|
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
|
|
||||||
rb.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
|
// account could not be created and relevant numerics have been dispatched, abort
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != errAccountCreation {
|
if err != errAccountCreation {
|
||||||
@ -131,87 +106,32 @@ func (server *Server) nickservRegisterHandler(client *Client, username, passphra
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// store details
|
err = server.accounts.Register(client, account, "", "", passphrase, client.certfp)
|
||||||
err = server.store.Update(func(tx *buntdb.Tx) error {
|
if err == nil {
|
||||||
// certfp special lookup key
|
err = server.accounts.Verify(client, casefoldedAccount, "")
|
||||||
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
|
// details could not be stored and relevant numerics have been dispatched, abort
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMsg := "Could not register"
|
errMsg := "Could not register"
|
||||||
if err == errCertfpAlreadyExists {
|
if err == errCertfpAlreadyExists {
|
||||||
errMsg = "An account already exists for your certificate fingerprint"
|
errMsg = "An account already exists for your certificate fingerprint"
|
||||||
|
} else if err == errAccountAlreadyRegistered {
|
||||||
|
errMsg = "Account already exists"
|
||||||
}
|
}
|
||||||
rb.Notice(errMsg)
|
rb.Notice(client.t(errMsg))
|
||||||
removeFailedAccRegisterData(server.store, casefoldedAccount)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = server.store.Update(func(tx *buntdb.Tx) error {
|
rb.Notice(client.t("Account created"))
|
||||||
tx.Set(fmt.Sprintf(keyAccountVerified, casefoldedAccount), "1", nil)
|
rb.Add(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, casefoldedAccount, fmt.Sprintf(client.t("You are now logged in as %s"), casefoldedAccount))
|
||||||
|
rb.Add(nil, server.name, RPL_SASLSUCCESS, client.nick, client.t("Authentication successful"))
|
||||||
// load acct info inside store tx
|
server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Account registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), casefoldedAccount, client.nickMaskString))
|
||||||
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
|
|
||||||
|
|
||||||
rb.Notice(client.t("Account created"))
|
|
||||||
rb.Add(nil, server.name, RPL_LOGGEDIN, client.nick, client.nickMaskString, account.Name, fmt.Sprintf(client.t("You are now logged in as %s"), account.Name))
|
|
||||||
rb.Add(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 {
|
|
||||||
rb.Notice(client.t("Account registration failed"))
|
|
||||||
removeFailedAccRegisterData(server.store, casefoldedAccount)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) nickservIdentifyHandler(client *Client, username, passphrase string, rb *ResponseBuffer) {
|
func (server *Server) nickservIdentifyHandler(client *Client, username, passphrase string, rb *ResponseBuffer) {
|
||||||
// fail out if we need to
|
// fail out if we need to
|
||||||
if !server.accountAuthenticationEnabled {
|
if !server.AccountConfig().AuthenticationEnabled {
|
||||||
rb.Notice(client.t("Login has been disabled"))
|
rb.Notice(client.t("Login has been disabled"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -219,45 +139,13 @@ func (server *Server) nickservIdentifyHandler(client *Client, username, passphra
|
|||||||
// try passphrase
|
// try passphrase
|
||||||
if username != "" && passphrase != "" {
|
if username != "" && passphrase != "" {
|
||||||
// keep it the same as in the ACC CREATE stage
|
// keep it the same as in the ACC CREATE stage
|
||||||
accountKey, err := CasefoldName(username)
|
accountName, err := CasefoldName(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rb.Notice(client.t("Could not login with your username/password"))
|
rb.Notice(client.t("Could not login with your username/password"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// load and check acct data all in one update to prevent races.
|
err = server.accounts.AuthenticateByPassphrase(client, accountName, passphrase)
|
||||||
// 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 {
|
if err == nil {
|
||||||
rb.Notice(fmt.Sprintf(client.t("You're now logged in as %s"), accountName))
|
rb.Notice(fmt.Sprintf(client.t("You're now logged in as %s"), accountName))
|
||||||
return
|
return
|
||||||
@ -265,48 +153,11 @@ func (server *Server) nickservIdentifyHandler(client *Client, username, passphra
|
|||||||
}
|
}
|
||||||
|
|
||||||
// try certfp
|
// try certfp
|
||||||
certfp := client.certfp
|
if client.certfp != "" {
|
||||||
if certfp != "" {
|
err := server.accounts.AuthenticateByCertFP(client)
|
||||||
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 {
|
if err == nil {
|
||||||
rb.Notice(fmt.Sprintf(client.t("You're now logged in as %s"), accountName))
|
rb.Notice(fmt.Sprintf(client.t("You're now logged in as %s"), client.AccountName()))
|
||||||
|
// TODO more notices?
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,11 +43,11 @@ func (rb *ResponseBuffer) Add(tags *map[string]ircmsg.TagValue, prefix string, c
|
|||||||
// AddFromClient adds a new message from a specific client to our queue.
|
// AddFromClient adds a new message from a specific client to our queue.
|
||||||
func (rb *ResponseBuffer) AddFromClient(msgid string, from *Client, tags *map[string]ircmsg.TagValue, command string, params ...string) {
|
func (rb *ResponseBuffer) AddFromClient(msgid string, from *Client, tags *map[string]ircmsg.TagValue, command string, params ...string) {
|
||||||
// attach account-tag
|
// attach account-tag
|
||||||
if rb.target.capabilities.Has(caps.AccountTag) && from.account != &NoAccount {
|
if rb.target.capabilities.Has(caps.AccountTag) && from.LoggedIntoAccount() {
|
||||||
if tags == nil {
|
if tags == nil {
|
||||||
tags = ircmsg.MakeTags("account", from.account.Name)
|
tags = ircmsg.MakeTags("account", from.AccountName())
|
||||||
} else {
|
} else {
|
||||||
(*tags)["account"] = ircmsg.MakeTagValue(from.account.Name)
|
(*tags)["account"] = ircmsg.MakeTagValue(from.AccountName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// attach message-id
|
// attach message-id
|
||||||
|
130
irc/server.go
130
irc/server.go
@ -87,49 +87,48 @@ type ListenerWrapper struct {
|
|||||||
|
|
||||||
// Server is the main Oragono server.
|
// Server is the main Oragono server.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
accountAuthenticationEnabled bool
|
accountConfig *AccountConfig
|
||||||
accountRegistration *AccountRegistration
|
accounts *AccountManager
|
||||||
accounts map[string]*ClientAccount
|
batches *BatchManager
|
||||||
batches *BatchManager
|
channelRegistrationEnabled bool
|
||||||
channelRegistrationEnabled bool
|
channels *ChannelManager
|
||||||
channels *ChannelManager
|
channelRegistry *ChannelRegistry
|
||||||
channelRegistry *ChannelRegistry
|
checkIdent bool
|
||||||
checkIdent bool
|
clients *ClientManager
|
||||||
clients *ClientManager
|
configFilename string
|
||||||
configFilename string
|
configurableStateMutex sync.RWMutex // tier 1; generic protection for server state modified by rehash()
|
||||||
configurableStateMutex sync.RWMutex // tier 1; generic protection for server state modified by rehash()
|
connectionLimiter *connection_limits.Limiter
|
||||||
connectionLimiter *connection_limits.Limiter
|
connectionThrottler *connection_limits.Throttler
|
||||||
connectionThrottler *connection_limits.Throttler
|
ctime time.Time
|
||||||
ctime time.Time
|
defaultChannelModes modes.Modes
|
||||||
defaultChannelModes modes.Modes
|
dlines *DLineManager
|
||||||
dlines *DLineManager
|
loggingRawIO bool
|
||||||
loggingRawIO bool
|
isupport *isupport.List
|
||||||
isupport *isupport.List
|
klines *KLineManager
|
||||||
klines *KLineManager
|
languages *languages.Manager
|
||||||
languages *languages.Manager
|
limits Limits
|
||||||
limits Limits
|
listeners map[string]*ListenerWrapper
|
||||||
listeners map[string]*ListenerWrapper
|
logger *logger.Manager
|
||||||
logger *logger.Manager
|
MaxSendQBytes uint64
|
||||||
MaxSendQBytes uint64
|
monitorManager *MonitorManager
|
||||||
monitorManager *MonitorManager
|
motdLines []string
|
||||||
motdLines []string
|
name string
|
||||||
name string
|
nameCasefolded string
|
||||||
nameCasefolded string
|
networkName string
|
||||||
networkName string
|
operators map[string]Oper
|
||||||
operators map[string]Oper
|
operclasses map[string]OperClass
|
||||||
operclasses map[string]OperClass
|
password []byte
|
||||||
password []byte
|
passwords *passwd.SaltedManager
|
||||||
passwords *passwd.SaltedManager
|
recoverFromErrors bool
|
||||||
recoverFromErrors bool
|
rehashMutex sync.Mutex // tier 4
|
||||||
rehashMutex sync.Mutex // tier 3
|
rehashSignal chan os.Signal
|
||||||
rehashSignal chan os.Signal
|
proxyAllowedFrom []string
|
||||||
proxyAllowedFrom []string
|
signals chan os.Signal
|
||||||
signals chan os.Signal
|
snomasks *SnoManager
|
||||||
snomasks *SnoManager
|
store *buntdb.DB
|
||||||
store *buntdb.DB
|
stsEnabled bool
|
||||||
stsEnabled bool
|
webirc []webircConfig
|
||||||
webirc []webircConfig
|
whoWas *WhoWasList
|
||||||
whoWas *WhoWasList
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -150,7 +149,6 @@ type clientConn struct {
|
|||||||
func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
|
func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
|
||||||
// initialize data structures
|
// initialize data structures
|
||||||
server := &Server{
|
server := &Server{
|
||||||
accounts: make(map[string]*ClientAccount),
|
|
||||||
batches: NewBatchManager(),
|
batches: NewBatchManager(),
|
||||||
channels: NewChannelManager(),
|
channels: NewChannelManager(),
|
||||||
clients: NewClientManager(),
|
clients: NewClientManager(),
|
||||||
@ -214,10 +212,10 @@ func (server *Server) setISupport() {
|
|||||||
isupport.Add("UTF8MAPPING", casemappingName)
|
isupport.Add("UTF8MAPPING", casemappingName)
|
||||||
|
|
||||||
// account registration
|
// account registration
|
||||||
if server.accountRegistration.Enabled {
|
if server.accountConfig.Registration.Enabled {
|
||||||
// 'none' isn't shown in the REGCALLBACKS vars
|
// 'none' isn't shown in the REGCALLBACKS vars
|
||||||
var enabledCallbacks []string
|
var enabledCallbacks []string
|
||||||
for _, name := range server.accountRegistration.EnabledCallbacks {
|
for _, name := range server.accountConfig.Registration.EnabledCallbacks {
|
||||||
if name != "*" {
|
if name != "*" {
|
||||||
enabledCallbacks = append(enabledCallbacks, name)
|
enabledCallbacks = append(enabledCallbacks, name)
|
||||||
}
|
}
|
||||||
@ -348,15 +346,8 @@ func (server *Server) createListener(addr string, tlsConfig *tls.Config) *Listen
|
|||||||
// make listener
|
// make listener
|
||||||
var listener net.Listener
|
var listener net.Listener
|
||||||
var err error
|
var err error
|
||||||
optionalUnixPrefix := "unix:"
|
addr = strings.TrimPrefix(addr, "unix:")
|
||||||
optionalPrefixLen := len(optionalUnixPrefix)
|
if strings.HasPrefix(addr, "/") {
|
||||||
if len(addr) >= optionalPrefixLen && strings.ToLower(addr[0:optionalPrefixLen]) == optionalUnixPrefix {
|
|
||||||
addr = addr[optionalPrefixLen:]
|
|
||||||
if len(addr) == 0 || addr[0] != '/' {
|
|
||||||
log.Fatal("Bad unix socket address", addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(addr) > 0 && addr[0] == '/' {
|
|
||||||
// https://stackoverflow.com/a/34881585
|
// https://stackoverflow.com/a/34881585
|
||||||
os.Remove(addr)
|
os.Remove(addr)
|
||||||
listener, err = net.Listen("unix", addr)
|
listener, err = net.Listen("unix", addr)
|
||||||
@ -478,7 +469,7 @@ func (server *Server) tryRegister(c *Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c.capabilities.Has(caps.ExtendedJoin) {
|
if c.capabilities.Has(caps.ExtendedJoin) {
|
||||||
c.Send(nil, c.nickMaskString, "JOIN", channel.name, c.account.Name, c.realname)
|
c.Send(nil, c.nickMaskString, "JOIN", channel.name, c.AccountName(), c.realname)
|
||||||
} else {
|
} else {
|
||||||
c.Send(nil, c.nickMaskString, "JOIN", channel.name)
|
c.Send(nil, c.nickMaskString, "JOIN", channel.name)
|
||||||
}
|
}
|
||||||
@ -630,9 +621,8 @@ func (client *Client) getWhoisOf(target *Client, rb *ResponseBuffer) {
|
|||||||
if target.flags[modes.TLS] {
|
if target.flags[modes.TLS] {
|
||||||
rb.Add(nil, client.server.name, RPL_WHOISSECURE, client.nick, target.nick, client.t("is using a secure connection"))
|
rb.Add(nil, client.server.name, RPL_WHOISSECURE, client.nick, target.nick, client.t("is using a secure connection"))
|
||||||
}
|
}
|
||||||
accountName := target.AccountName()
|
if target.LoggedIntoAccount() {
|
||||||
if accountName != "" {
|
rb.Add(nil, client.server.name, RPL_WHOISACCOUNT, client.nick, client.AccountName(), client.t("is logged in as"))
|
||||||
rb.Add(nil, client.server.name, RPL_WHOISACCOUNT, client.nick, accountName, client.t("is logged in as"))
|
|
||||||
}
|
}
|
||||||
if target.flags[modes.Bot] {
|
if target.flags[modes.Bot] {
|
||||||
rb.Add(nil, client.server.name, RPL_WHOISBOT, client.nick, target.nick, ircfmt.Unescape(fmt.Sprintf(client.t("is a $bBot$b on %s"), client.server.networkName)))
|
rb.Add(nil, client.server.name, RPL_WHOISBOT, client.nick, target.nick, ircfmt.Unescape(fmt.Sprintf(client.t("is a $bBot$b on %s"), client.server.networkName)))
|
||||||
@ -803,18 +793,28 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
|
|||||||
server.languages = lm
|
server.languages = lm
|
||||||
|
|
||||||
// SASL
|
// SASL
|
||||||
if config.Accounts.AuthenticationEnabled && !server.accountAuthenticationEnabled {
|
oldAccountConfig := server.AccountConfig()
|
||||||
|
authPreviouslyEnabled := oldAccountConfig != nil && !oldAccountConfig.AuthenticationEnabled
|
||||||
|
if config.Accounts.AuthenticationEnabled && !authPreviouslyEnabled {
|
||||||
// enabling SASL
|
// enabling SASL
|
||||||
SupportedCapabilities.Enable(caps.SASL)
|
SupportedCapabilities.Enable(caps.SASL)
|
||||||
CapValues.Set(caps.SASL, "PLAIN,EXTERNAL")
|
CapValues.Set(caps.SASL, "PLAIN,EXTERNAL")
|
||||||
addedCaps.Add(caps.SASL)
|
addedCaps.Add(caps.SASL)
|
||||||
}
|
} else if !config.Accounts.AuthenticationEnabled && authPreviouslyEnabled {
|
||||||
if !config.Accounts.AuthenticationEnabled && server.accountAuthenticationEnabled {
|
|
||||||
// disabling SASL
|
// disabling SASL
|
||||||
SupportedCapabilities.Disable(caps.SASL)
|
SupportedCapabilities.Disable(caps.SASL)
|
||||||
removedCaps.Add(caps.SASL)
|
removedCaps.Add(caps.SASL)
|
||||||
}
|
}
|
||||||
server.accountAuthenticationEnabled = config.Accounts.AuthenticationEnabled
|
|
||||||
|
server.configurableStateMutex.Lock()
|
||||||
|
server.accountConfig = &config.Accounts
|
||||||
|
server.configurableStateMutex.Unlock()
|
||||||
|
|
||||||
|
nickReservationPreviouslyDisabled := oldAccountConfig != nil && oldAccountConfig.NickReservation == NickReservationDisabled
|
||||||
|
nickReservationNowEnabled := config.Accounts.NickReservation != NickReservationDisabled
|
||||||
|
if nickReservationPreviouslyDisabled && nickReservationNowEnabled {
|
||||||
|
server.accounts.buildNickToAccountIndex()
|
||||||
|
}
|
||||||
|
|
||||||
// STS
|
// STS
|
||||||
stsValue := config.Server.STS.Value()
|
stsValue := config.Server.STS.Value()
|
||||||
@ -902,8 +902,6 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
|
|||||||
server.checkIdent = config.Server.CheckIdent
|
server.checkIdent = config.Server.CheckIdent
|
||||||
|
|
||||||
// registration
|
// registration
|
||||||
accountReg := NewAccountRegistration(config.Accounts.Registration)
|
|
||||||
server.accountRegistration = &accountReg
|
|
||||||
server.channelRegistrationEnabled = config.Channels.Registration.Enabled
|
server.channelRegistrationEnabled = config.Channels.Registration.Enabled
|
||||||
|
|
||||||
server.defaultChannelModes = ParseDefaultChannelModes(config)
|
server.defaultChannelModes = ParseDefaultChannelModes(config)
|
||||||
@ -1043,6 +1041,8 @@ func (server *Server) loadDatastore(datastorePath string) error {
|
|||||||
|
|
||||||
server.channelRegistry = NewChannelRegistry(server)
|
server.channelRegistry = NewChannelRegistry(server)
|
||||||
|
|
||||||
|
server.accounts = NewAccountManager(server)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,6 +159,14 @@ accounts:
|
|||||||
# is account authentication enabled?
|
# is account authentication enabled?
|
||||||
authentication-enabled: true
|
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
|
||||||
|
|
||||||
# channel options
|
# channel options
|
||||||
channels:
|
channels:
|
||||||
# modes that are set when new channels are created
|
# modes that are set when new channels are created
|
||||||
@ -210,6 +218,7 @@ oper-classes:
|
|||||||
capabilities:
|
capabilities:
|
||||||
- "oper:rehash"
|
- "oper:rehash"
|
||||||
- "oper:die"
|
- "oper:die"
|
||||||
|
- "unregister"
|
||||||
- "samode"
|
- "samode"
|
||||||
|
|
||||||
# ircd operators
|
# ircd operators
|
||||||
|
Loading…
Reference in New Issue
Block a user