mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-13 07:29:30 +01:00
nickserv: implement GHOST, GROUP, DROP, and INFO
This commit is contained in:
parent
b211fd35da
commit
a022befffe
244
irc/accounts.go
244
irc/accounts.go
@ -29,6 +29,7 @@ const (
|
|||||||
keyAccountName = "account.name %s" // stores the 'preferred name' of the account, not casemapped
|
keyAccountName = "account.name %s" // stores the 'preferred name' of the account, not casemapped
|
||||||
keyAccountRegTime = "account.registered.time %s"
|
keyAccountRegTime = "account.registered.time %s"
|
||||||
keyAccountCredentials = "account.credentials %s"
|
keyAccountCredentials = "account.credentials %s"
|
||||||
|
keyAccountAdditionalNicks = "account.additionalnicks %s"
|
||||||
keyCertToAccount = "account.creds.certfp %s"
|
keyCertToAccount = "account.creds.certfp %s"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -75,6 +76,12 @@ func (am *AccountManager) buildNickToAccountIndex() {
|
|||||||
if _, err := tx.Get(fmt.Sprintf(keyAccountVerified, accountName)); err == nil {
|
if _, err := tx.Get(fmt.Sprintf(keyAccountVerified, accountName)); err == nil {
|
||||||
result[accountName] = accountName
|
result[accountName] = accountName
|
||||||
}
|
}
|
||||||
|
if rawNicks, err := tx.Get(fmt.Sprintf(keyAccountAdditionalNicks, accountName)); err == nil {
|
||||||
|
additionalNicks := unmarshalReservedNicks(rawNicks)
|
||||||
|
for _, nick := range additionalNicks {
|
||||||
|
result[nick] = accountName
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
@ -91,7 +98,12 @@ func (am *AccountManager) buildNickToAccountIndex() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) NickToAccount(cfnick string) string {
|
func (am *AccountManager) NickToAccount(nick string) string {
|
||||||
|
cfnick, err := CasefoldName(nick)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
am.RLock()
|
am.RLock()
|
||||||
defer am.RUnlock()
|
defer am.RUnlock()
|
||||||
return am.nickToAccount[cfnick]
|
return am.nickToAccount[cfnick]
|
||||||
@ -325,13 +337,92 @@ func (am *AccountManager) Verify(client *Client, account string, code string) er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName string, passphrase string) error {
|
func marshalReservedNicks(nicks []string) string {
|
||||||
casefoldedAccount, err := CasefoldName(accountName)
|
return strings.Join(nicks, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalReservedNicks(nicks string) (result []string) {
|
||||||
|
if nicks == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return strings.Split(nicks, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) SetNickReserved(client *Client, nick string, reserve bool) error {
|
||||||
|
cfnick, err := CasefoldName(nick)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errAccountDoesNotExist
|
return errAccountNickReservationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := am.LoadAccount(casefoldedAccount)
|
// sanity check so we don't persist bad data
|
||||||
|
account := client.Account()
|
||||||
|
if account == "" || cfnick == "" || !am.server.AccountConfig().NickReservation.Enabled {
|
||||||
|
return errAccountNickReservationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := am.server.AccountConfig().NickReservation.AdditionalNickLimit
|
||||||
|
|
||||||
|
am.serialCacheUpdateMutex.Lock()
|
||||||
|
defer am.serialCacheUpdateMutex.Unlock()
|
||||||
|
|
||||||
|
// the cache is in sync with the DB while we hold serialCacheUpdateMutex
|
||||||
|
accountForNick := am.NickToAccount(cfnick)
|
||||||
|
if reserve && accountForNick != "" {
|
||||||
|
return errNicknameReserved
|
||||||
|
} else if !reserve && accountForNick != account {
|
||||||
|
return errAccountNickReservationFailed
|
||||||
|
} else if !reserve && cfnick == account {
|
||||||
|
return errAccountCantDropPrimaryNick
|
||||||
|
}
|
||||||
|
|
||||||
|
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, account)
|
||||||
|
err = am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||||
|
rawNicks, err := tx.Get(nicksKey)
|
||||||
|
if err != nil && err != buntdb.ErrNotFound {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nicks := unmarshalReservedNicks(rawNicks)
|
||||||
|
|
||||||
|
if reserve {
|
||||||
|
if len(nicks) >= limit {
|
||||||
|
return errAccountTooManyNicks
|
||||||
|
}
|
||||||
|
nicks = append(nicks, cfnick)
|
||||||
|
} else {
|
||||||
|
var newNicks []string
|
||||||
|
for _, reservedNick := range nicks {
|
||||||
|
if reservedNick != cfnick {
|
||||||
|
newNicks = append(newNicks, reservedNick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nicks = newNicks
|
||||||
|
}
|
||||||
|
|
||||||
|
marshaledNicks := marshalReservedNicks(nicks)
|
||||||
|
_, _, err = tx.Set(nicksKey, string(marshaledNicks), nil)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == errAccountTooManyNicks {
|
||||||
|
return err
|
||||||
|
} else if err != nil {
|
||||||
|
return errAccountNickReservationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
// success
|
||||||
|
am.Lock()
|
||||||
|
defer am.Unlock()
|
||||||
|
if reserve {
|
||||||
|
am.nickToAccount[cfnick] = account
|
||||||
|
} else {
|
||||||
|
delete(am.nickToAccount, cfnick)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName string, passphrase string) error {
|
||||||
|
account, err := am.LoadAccount(accountName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -350,7 +441,13 @@ func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName s
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) LoadAccount(casefoldedAccount string) (result ClientAccount, err error) {
|
func (am *AccountManager) LoadAccount(accountName string) (result ClientAccount, err error) {
|
||||||
|
casefoldedAccount, err := CasefoldName(accountName)
|
||||||
|
if err != nil {
|
||||||
|
err = errAccountDoesNotExist
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var raw rawClientAccount
|
var raw rawClientAccount
|
||||||
am.server.store.View(func(tx *buntdb.Tx) error {
|
am.server.store.View(func(tx *buntdb.Tx) error {
|
||||||
raw, err = am.loadRawAccount(tx, casefoldedAccount)
|
raw, err = am.loadRawAccount(tx, casefoldedAccount)
|
||||||
@ -369,6 +466,7 @@ func (am *AccountManager) LoadAccount(casefoldedAccount string) (result ClientAc
|
|||||||
err = errAccountDoesNotExist
|
err = errAccountDoesNotExist
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
result.AdditionalNicks = unmarshalReservedNicks(raw.AdditionalNicks)
|
||||||
result.Verified = raw.Verified
|
result.Verified = raw.Verified
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -380,6 +478,7 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
|||||||
credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
|
credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
|
||||||
verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
|
verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
|
||||||
callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
||||||
|
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
||||||
|
|
||||||
_, e := tx.Get(accountKey)
|
_, e := tx.Get(accountKey)
|
||||||
if e == buntdb.ErrNotFound {
|
if e == buntdb.ErrNotFound {
|
||||||
@ -391,6 +490,7 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
|||||||
result.RegisteredAt, _ = tx.Get(registeredTimeKey)
|
result.RegisteredAt, _ = tx.Get(registeredTimeKey)
|
||||||
result.Credentials, _ = tx.Get(credentialsKey)
|
result.Credentials, _ = tx.Get(credentialsKey)
|
||||||
result.Callback, _ = tx.Get(callbackKey)
|
result.Callback, _ = tx.Get(callbackKey)
|
||||||
|
result.AdditionalNicks, _ = tx.Get(nicksKey)
|
||||||
|
|
||||||
if _, e = tx.Get(verifiedKey); e == nil {
|
if _, e = tx.Get(verifiedKey); e == nil {
|
||||||
result.Verified = true
|
result.Verified = true
|
||||||
@ -412,51 +512,56 @@ func (am *AccountManager) Unregister(account string) error {
|
|||||||
callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
||||||
verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
|
verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
|
||||||
verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
|
verifiedKey := fmt.Sprintf(keyAccountVerified, casefoldedAccount)
|
||||||
|
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
||||||
|
|
||||||
var clients []*Client
|
var clients []*Client
|
||||||
|
|
||||||
func() {
|
var credText string
|
||||||
var credText string
|
var rawNicks string
|
||||||
|
|
||||||
am.serialCacheUpdateMutex.Lock()
|
am.serialCacheUpdateMutex.Lock()
|
||||||
defer am.serialCacheUpdateMutex.Unlock()
|
defer am.serialCacheUpdateMutex.Unlock()
|
||||||
|
|
||||||
am.server.store.Update(func(tx *buntdb.Tx) error {
|
am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||||
tx.Delete(accountKey)
|
tx.Delete(accountKey)
|
||||||
tx.Delete(accountNameKey)
|
tx.Delete(accountNameKey)
|
||||||
tx.Delete(verifiedKey)
|
tx.Delete(verifiedKey)
|
||||||
tx.Delete(registeredTimeKey)
|
tx.Delete(registeredTimeKey)
|
||||||
tx.Delete(callbackKey)
|
tx.Delete(callbackKey)
|
||||||
tx.Delete(verificationCodeKey)
|
tx.Delete(verificationCodeKey)
|
||||||
credText, err = tx.Get(credentialsKey)
|
rawNicks, _ = tx.Get(nicksKey)
|
||||||
tx.Delete(credentialsKey)
|
tx.Delete(nicksKey)
|
||||||
return nil
|
credText, err = tx.Get(credentialsKey)
|
||||||
})
|
tx.Delete(credentialsKey)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var creds AccountCredentials
|
var creds AccountCredentials
|
||||||
if err = json.Unmarshal([]byte(credText), &creds); err == nil && creds.Certificate != "" {
|
if err = json.Unmarshal([]byte(credText), &creds); err == nil && creds.Certificate != "" {
|
||||||
certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
|
certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
|
||||||
am.server.store.Update(func(tx *buntdb.Tx) error {
|
am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||||
if account, err := tx.Get(certFPKey); err == nil && account == casefoldedAccount {
|
if account, err := tx.Get(certFPKey); err == nil && account == casefoldedAccount {
|
||||||
tx.Delete(certFPKey)
|
tx.Delete(certFPKey)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
am.Lock()
|
additionalNicks := unmarshalReservedNicks(rawNicks)
|
||||||
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)
|
|
||||||
}()
|
|
||||||
|
|
||||||
|
am.Lock()
|
||||||
|
defer am.Unlock()
|
||||||
|
|
||||||
|
clients = am.accountToClients[casefoldedAccount]
|
||||||
|
delete(am.accountToClients, casefoldedAccount)
|
||||||
|
delete(am.nickToAccount, casefoldedAccount)
|
||||||
|
for _, nick := range additionalNicks {
|
||||||
|
delete(am.nickToAccount, nick)
|
||||||
|
}
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
client.LogoutOfAccount()
|
am.logoutOfAccount(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -498,29 +603,25 @@ func (am *AccountManager) AuthenticateByCertFP(client *Client) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) Login(client *Client, account string) {
|
func (am *AccountManager) Login(client *Client, account string) {
|
||||||
client.LoginToAccount(account)
|
|
||||||
|
|
||||||
casefoldedAccount, _ := CasefoldName(account)
|
|
||||||
am.Lock()
|
am.Lock()
|
||||||
defer am.Unlock()
|
defer am.Unlock()
|
||||||
|
|
||||||
|
am.loginToAccount(client, account)
|
||||||
|
casefoldedAccount := client.Account()
|
||||||
am.accountToClients[casefoldedAccount] = append(am.accountToClients[casefoldedAccount], client)
|
am.accountToClients[casefoldedAccount] = append(am.accountToClients[casefoldedAccount], client)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) Logout(client *Client) {
|
func (am *AccountManager) Logout(client *Client) {
|
||||||
casefoldedAccount := client.Account()
|
|
||||||
if casefoldedAccount == "" || casefoldedAccount == "*" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client.LogoutOfAccount()
|
|
||||||
|
|
||||||
am.Lock()
|
am.Lock()
|
||||||
defer am.Unlock()
|
defer am.Unlock()
|
||||||
|
|
||||||
if client.LoggedIntoAccount() {
|
casefoldedAccount := client.Account()
|
||||||
|
if casefoldedAccount == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
am.logoutOfAccount(client)
|
||||||
|
|
||||||
clients := am.accountToClients[casefoldedAccount]
|
clients := am.accountToClients[casefoldedAccount]
|
||||||
if len(clients) <= 1 {
|
if len(clients) <= 1 {
|
||||||
delete(am.accountToClients, casefoldedAccount)
|
delete(am.accountToClients, casefoldedAccount)
|
||||||
@ -559,42 +660,45 @@ 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
|
||||||
Credentials AccountCredentials
|
Credentials AccountCredentials
|
||||||
Verified bool
|
Verified bool
|
||||||
|
AdditionalNicks []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// convenience for passing around raw serialized account data
|
// convenience for passing around raw serialized account data
|
||||||
type rawClientAccount struct {
|
type rawClientAccount struct {
|
||||||
Name string
|
Name string
|
||||||
RegisteredAt string
|
RegisteredAt string
|
||||||
Credentials string
|
Credentials string
|
||||||
Callback string
|
Callback string
|
||||||
Verified bool
|
Verified bool
|
||||||
|
AdditionalNicks string
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginToAccount logs the client into the given account.
|
// loginToAccount logs the client into the given account.
|
||||||
func (client *Client) LoginToAccount(account string) {
|
func (am *AccountManager) loginToAccount(client *Client, account string) {
|
||||||
changed := client.SetAccountName(account)
|
changed := client.SetAccountName(account)
|
||||||
if changed {
|
if changed {
|
||||||
client.nickTimer.Touch()
|
go client.nickTimer.Touch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogoutOfAccount logs the client out of their current account.
|
// logoutOfAccount logs the client out of their current account.
|
||||||
func (client *Client) LogoutOfAccount() {
|
func (am *AccountManager) logoutOfAccount(client *Client) {
|
||||||
if client.Account() == "" {
|
if client.Account() == "" {
|
||||||
// already logged out
|
// already logged out
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
client.SetAccountName("")
|
client.SetAccountName("")
|
||||||
client.nickTimer.Touch()
|
go client.nickTimer.Touch()
|
||||||
|
|
||||||
// dispatch account-notify
|
// dispatch account-notify
|
||||||
// TODO: doing the I/O here is kind of a kludge, let's move this somewhere else
|
// TODO: doing the I/O here is kind of a kludge, let's move this somewhere else
|
||||||
for friend := range client.Friends(caps.AccountNotify) {
|
go func() {
|
||||||
friend.Send(nil, client.nickMaskString, "ACCOUNT", "*")
|
for friend := range client.Friends(caps.AccountNotify) {
|
||||||
}
|
friend.Send(nil, client.NickMaskString(), "ACCOUNT", "*")
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,10 +117,11 @@ func (nr *NickReservationMethod) UnmarshalYAML(unmarshal func(interface{}) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NickReservationConfig struct {
|
type NickReservationConfig struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Method NickReservationMethod
|
AdditionalNickLimit int `yaml:"additional-nick-limit"`
|
||||||
RenameTimeout time.Duration `yaml:"rename-timeout"`
|
Method NickReservationMethod
|
||||||
RenamePrefix string `yaml:"rename-prefix"`
|
RenameTimeout time.Duration `yaml:"rename-timeout"`
|
||||||
|
RenamePrefix string `yaml:"rename-prefix"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChannelRegistrationConfig controls channel registration.
|
// ChannelRegistrationConfig controls channel registration.
|
||||||
|
@ -12,11 +12,15 @@ var (
|
|||||||
errAccountAlreadyRegistered = errors.New("Account already exists")
|
errAccountAlreadyRegistered = errors.New("Account already exists")
|
||||||
errAccountCreation = errors.New("Account could not be created")
|
errAccountCreation = errors.New("Account could not be created")
|
||||||
errAccountDoesNotExist = errors.New("Account does not exist")
|
errAccountDoesNotExist = errors.New("Account does not exist")
|
||||||
|
errAccountNotLoggedIn = errors.New("You're not logged into an account")
|
||||||
errAccountVerificationFailed = errors.New("Account verification failed")
|
errAccountVerificationFailed = errors.New("Account verification failed")
|
||||||
errAccountVerificationInvalidCode = errors.New("Invalid account verification code")
|
errAccountVerificationInvalidCode = errors.New("Invalid account verification code")
|
||||||
errAccountUnverified = errors.New("Account is not yet verified")
|
errAccountUnverified = errors.New("Account is not yet verified")
|
||||||
errAccountAlreadyVerified = errors.New("Account is already verified")
|
errAccountAlreadyVerified = errors.New("Account is already verified")
|
||||||
errAccountInvalidCredentials = errors.New("Invalid account credentials")
|
errAccountInvalidCredentials = errors.New("Invalid account credentials")
|
||||||
|
errAccountTooManyNicks = errors.New("Account has too many reserved nicks")
|
||||||
|
errAccountNickReservationFailed = errors.New("Could not (un)reserve nick")
|
||||||
|
errAccountCantDropPrimaryNick = errors.New("Can't unreserve primary nickname")
|
||||||
errCallbackFailed = errors.New("Account verification could not be sent")
|
errCallbackFailed = errors.New("Account verification could not be sent")
|
||||||
errCertfpAlreadyExists = errors.New("An account already exists with your certificate")
|
errCertfpAlreadyExists = errors.New("An account already exists with your certificate")
|
||||||
errChannelAlreadyRegistered = errors.New("Channel is already registered")
|
errChannelAlreadyRegistered = errors.New("Channel is already registered")
|
||||||
|
@ -352,15 +352,8 @@ func authPlainHandler(server *Server, client *Client, mechanism string, value []
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep it the same as in the REG CREATE stage
|
|
||||||
accountKey, err := CasefoldName(accountKey)
|
|
||||||
if err != nil {
|
|
||||||
rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, client.t("SASL authentication failed: Bad account name"))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
password := string(splitValue[2])
|
password := string(splitValue[2])
|
||||||
err = server.accounts.AuthenticateByPassphrase(client, accountKey, password)
|
err := server.accounts.AuthenticateByPassphrase(client, accountKey, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := authErrorToMessage(server, err)
|
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)))
|
rb.Add(nil, server.name, ERR_SASLFAIL, client.nick, fmt.Sprintf("%s: %s", client.t("SASL authentication failed"), client.t(msg)))
|
||||||
|
123
irc/nickserv.go
123
irc/nickserv.go
@ -28,7 +28,18 @@ Leave out [username] if you're unregistering the user you're currently logged in
|
|||||||
To login to an account:
|
To login to an account:
|
||||||
/NS IDENTIFY [username password]
|
/NS IDENTIFY [username password]
|
||||||
Leave out [username password] to use your client certificate fingerprint. Otherwise,
|
Leave out [username password] to use your client certificate fingerprint. Otherwise,
|
||||||
the given username and password will be used.`
|
the given username and password will be used.
|
||||||
|
|
||||||
|
To see account information:
|
||||||
|
/NS INFO [username]
|
||||||
|
Leave out [username] to see your own account information.
|
||||||
|
|
||||||
|
To associate your current nick with the account you're logged into:
|
||||||
|
/NS GROUP
|
||||||
|
|
||||||
|
To disassociate a nick with the account you're logged into:
|
||||||
|
/NS DROP [nickname]
|
||||||
|
Leave out [nickname] to drop your association with your current nickname.`
|
||||||
|
|
||||||
// extractParam extracts a parameter from the given string, returning the param and the rest of the string.
|
// extractParam extracts a parameter from the given string, returning the param and the rest of the string.
|
||||||
func extractParam(line string) (string, string) {
|
func extractParam(line string) (string, string) {
|
||||||
@ -69,6 +80,17 @@ func (server *Server) nickservPrivmsgHandler(client *Client, message string, rb
|
|||||||
} else if command == "unregister" {
|
} else if command == "unregister" {
|
||||||
username, _ := extractParam(params)
|
username, _ := extractParam(params)
|
||||||
server.nickservUnregisterHandler(client, username, rb)
|
server.nickservUnregisterHandler(client, username, rb)
|
||||||
|
} else if command == "ghost" {
|
||||||
|
nick, _ := extractParam(params)
|
||||||
|
server.nickservGhostHandler(client, nick, rb)
|
||||||
|
} else if command == "info" {
|
||||||
|
nick, _ := extractParam(params)
|
||||||
|
server.nickservInfoHandler(client, nick, rb)
|
||||||
|
} else if command == "group" {
|
||||||
|
server.nickservGroupHandler(client, rb)
|
||||||
|
} else if command == "drop" {
|
||||||
|
nick, _ := extractParam(params)
|
||||||
|
server.nickservDropHandler(client, nick, rb)
|
||||||
} else {
|
} else {
|
||||||
rb.Notice(client.t("Command not recognised. To see the available commands, run /NS HELP"))
|
rb.Notice(client.t("Command not recognised. To see the available commands, run /NS HELP"))
|
||||||
}
|
}
|
||||||
@ -158,7 +180,7 @@ func (server *Server) nickservRegisterHandler(client *Client, username, email, p
|
|||||||
config := server.AccountConfig()
|
config := server.AccountConfig()
|
||||||
var callbackNamespace, callbackValue string
|
var callbackNamespace, callbackValue string
|
||||||
noneCallbackAllowed := false
|
noneCallbackAllowed := false
|
||||||
for _, callback := range(config.Registration.EnabledCallbacks) {
|
for _, callback := range config.Registration.EnabledCallbacks {
|
||||||
if callback == "*" {
|
if callback == "*" {
|
||||||
noneCallbackAllowed = true
|
noneCallbackAllowed = true
|
||||||
}
|
}
|
||||||
@ -233,3 +255,100 @@ func (server *Server) nickservIdentifyHandler(client *Client, username, passphra
|
|||||||
rb.Notice(client.t("Could not login with your TLS certificate or supplied username/password"))
|
rb.Notice(client.t("Could not login with your TLS certificate or supplied username/password"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (server *Server) nickservGhostHandler(client *Client, nick string, rb *ResponseBuffer) {
|
||||||
|
if !server.AccountConfig().NickReservation.Enabled {
|
||||||
|
rb.Notice(client.t("Nickname reservation is disabled"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
account := client.Account()
|
||||||
|
if account == "" || server.accounts.NickToAccount(nick) != account {
|
||||||
|
rb.Notice(client.t("You don't own that nick"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ghost := server.clients.Get(nick)
|
||||||
|
if ghost == nil {
|
||||||
|
rb.Notice(client.t("No such nick"))
|
||||||
|
return
|
||||||
|
} else if ghost == client {
|
||||||
|
rb.Notice(client.t("You can't GHOST yourself (try /QUIT instead)"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ghost.Quit(fmt.Sprintf(ghost.t("GHOSTed by %s"), client.Nick()))
|
||||||
|
ghost.destroy(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *Server) nickservGroupHandler(client *Client, rb *ResponseBuffer) {
|
||||||
|
if !server.AccountConfig().NickReservation.Enabled {
|
||||||
|
rb.Notice(client.t("Nickname reservation is disabled"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
account := client.Account()
|
||||||
|
if account == "" {
|
||||||
|
rb.Notice(client.t("You're not logged into an account"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nick := client.NickCasefolded()
|
||||||
|
err := server.accounts.SetNickReserved(client, nick, true)
|
||||||
|
if err == nil {
|
||||||
|
rb.Notice(fmt.Sprintf(client.t("Successfully grouped nick %s with your account"), nick))
|
||||||
|
} else if err == errAccountTooManyNicks {
|
||||||
|
rb.Notice(client.t("You have too many nicks reserved already (you can remove some with /NS DROP)"))
|
||||||
|
} else if err == errNicknameReserved {
|
||||||
|
rb.Notice(client.t("That nickname is already reserved"))
|
||||||
|
} else {
|
||||||
|
rb.Notice(client.t("Error reserving nickname"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *Server) nickservInfoHandler(client *Client, nick string, rb *ResponseBuffer) {
|
||||||
|
if nick == "" {
|
||||||
|
nick = client.Nick()
|
||||||
|
}
|
||||||
|
|
||||||
|
accountName := nick
|
||||||
|
if server.AccountConfig().NickReservation.Enabled {
|
||||||
|
accountName = server.accounts.NickToAccount(nick)
|
||||||
|
if accountName == "" {
|
||||||
|
rb.Notice(client.t("That nickname is not registered"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := server.accounts.LoadAccount(accountName)
|
||||||
|
if err != nil || !account.Verified {
|
||||||
|
rb.Notice(client.t("Account does not exist"))
|
||||||
|
}
|
||||||
|
|
||||||
|
rb.Notice(fmt.Sprintf(client.t("Account: %s"), account.Name))
|
||||||
|
registeredAt := account.RegisteredAt.Format("Jan 02, 2006 15:04:05Z")
|
||||||
|
rb.Notice(fmt.Sprintf(client.t("Registered at: %s"), registeredAt))
|
||||||
|
// TODO nicer formatting for this
|
||||||
|
for _, nick := range account.AdditionalNicks {
|
||||||
|
rb.Notice(fmt.Sprintf(client.t("Additional grouped nick: %s"), nick))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *Server) nickservDropHandler(client *Client, nick string, rb *ResponseBuffer) {
|
||||||
|
account := client.Account()
|
||||||
|
if account == "" {
|
||||||
|
rb.Notice(client.t("You're not logged into an account"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := server.accounts.SetNickReserved(client, nick, false)
|
||||||
|
if err == nil {
|
||||||
|
rb.Notice(fmt.Sprintf(client.t("Successfully ungrouped nick %s with your account"), nick))
|
||||||
|
} else if err == errAccountCantDropPrimaryNick {
|
||||||
|
rb.Notice(fmt.Sprintf(client.t("You can't ungroup your primary nickname (try unregistering your account instead)")))
|
||||||
|
} else if err == errAccountNickReservationFailed {
|
||||||
|
rb.Notice(fmt.Sprintf(client.t("You don't own that nick")))
|
||||||
|
} else {
|
||||||
|
rb.Notice(client.t("Error ungrouping nick"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -182,6 +182,9 @@ accounts:
|
|||||||
# is there any enforcement of reserved nicknames?
|
# is there any enforcement of reserved nicknames?
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
# how many nicknames, in addition to the account name, can be reserved?
|
||||||
|
additional-nick-limit: 2
|
||||||
|
|
||||||
# method describes how nickname reservation is handled
|
# method describes how nickname reservation is handled
|
||||||
# timeout: let the user change to the registered nickname, give them X seconds
|
# timeout: let the user change to the registered nickname, give them X seconds
|
||||||
# to login and then rename them if they haven't done so
|
# to login and then rename them if they haven't done so
|
||||||
|
Loading…
Reference in New Issue
Block a user