mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-26 13:59:44 +01:00
Merge pull request #499 from slingamn/prefs.11
user preference system plus two small fixes
This commit is contained in:
commit
6291a44350
205
irc/accounts.go
205
irc/accounts.go
@ -29,7 +29,7 @@ const (
|
|||||||
keyAccountRegTime = "account.registered.time %s"
|
keyAccountRegTime = "account.registered.time %s"
|
||||||
keyAccountCredentials = "account.credentials %s"
|
keyAccountCredentials = "account.credentials %s"
|
||||||
keyAccountAdditionalNicks = "account.additionalnicks %s"
|
keyAccountAdditionalNicks = "account.additionalnicks %s"
|
||||||
keyAccountEnforcement = "account.customenforcement %s"
|
keyAccountSettings = "account.settings %s"
|
||||||
keyAccountVHost = "account.vhost %s"
|
keyAccountVHost = "account.vhost %s"
|
||||||
keyCertToAccount = "account.creds.certfp %s"
|
keyCertToAccount = "account.creds.certfp %s"
|
||||||
keyAccountChannels = "account.channels %s"
|
keyAccountChannels = "account.channels %s"
|
||||||
@ -55,14 +55,14 @@ type AccountManager struct {
|
|||||||
accountToClients map[string][]*Client
|
accountToClients map[string][]*Client
|
||||||
nickToAccount map[string]string
|
nickToAccount map[string]string
|
||||||
skeletonToAccount map[string]string
|
skeletonToAccount map[string]string
|
||||||
accountToMethod map[string]NickReservationMethod
|
accountToMethod map[string]NickEnforcementMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) Initialize(server *Server) {
|
func (am *AccountManager) Initialize(server *Server) {
|
||||||
am.accountToClients = make(map[string][]*Client)
|
am.accountToClients = make(map[string][]*Client)
|
||||||
am.nickToAccount = make(map[string]string)
|
am.nickToAccount = make(map[string]string)
|
||||||
am.skeletonToAccount = make(map[string]string)
|
am.skeletonToAccount = make(map[string]string)
|
||||||
am.accountToMethod = make(map[string]NickReservationMethod)
|
am.accountToMethod = make(map[string]NickEnforcementMethod)
|
||||||
am.server = server
|
am.server = server
|
||||||
|
|
||||||
am.buildNickToAccountIndex()
|
am.buildNickToAccountIndex()
|
||||||
@ -76,7 +76,7 @@ func (am *AccountManager) buildNickToAccountIndex() {
|
|||||||
|
|
||||||
nickToAccount := make(map[string]string)
|
nickToAccount := make(map[string]string)
|
||||||
skeletonToAccount := make(map[string]string)
|
skeletonToAccount := make(map[string]string)
|
||||||
accountToMethod := make(map[string]NickReservationMethod)
|
accountToMethod := make(map[string]NickEnforcementMethod)
|
||||||
existsPrefix := fmt.Sprintf(keyAccountExists, "")
|
existsPrefix := fmt.Sprintf(keyAccountExists, "")
|
||||||
|
|
||||||
am.serialCacheUpdateMutex.Lock()
|
am.serialCacheUpdateMutex.Lock()
|
||||||
@ -109,12 +109,16 @@ func (am *AccountManager) buildNickToAccountIndex() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if methodStr, err := tx.Get(fmt.Sprintf(keyAccountEnforcement, account)); err == nil {
|
if rawPrefs, err := tx.Get(fmt.Sprintf(keyAccountSettings, account)); err == nil {
|
||||||
method, err := nickReservationFromString(methodStr)
|
var prefs AccountSettings
|
||||||
if err == nil {
|
err := json.Unmarshal([]byte(rawPrefs), &prefs)
|
||||||
accountToMethod[account] = method
|
if err == nil && prefs.NickEnforcement != NickEnforcementOptional {
|
||||||
|
accountToMethod[account] = prefs.NickEnforcement
|
||||||
|
} else {
|
||||||
|
am.server.logger.Error("internal", "corrupt account creds", account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
@ -180,36 +184,44 @@ func (am *AccountManager) NickToAccount(nick string) string {
|
|||||||
return am.nickToAccount[cfnick]
|
return am.nickToAccount[cfnick]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// given an account, combine stored enforcement method with the config settings
|
||||||
|
// to compute the actual enforcement method
|
||||||
|
func configuredEnforcementMethod(config *Config, storedMethod NickEnforcementMethod) (result NickEnforcementMethod) {
|
||||||
|
if !config.Accounts.NickReservation.Enabled {
|
||||||
|
return NickEnforcementNone
|
||||||
|
}
|
||||||
|
result = storedMethod
|
||||||
|
// if they don't have a custom setting, or customization is disabled, use the default
|
||||||
|
if result == NickEnforcementOptional || !config.Accounts.NickReservation.AllowCustomEnforcement {
|
||||||
|
result = config.Accounts.NickReservation.Method
|
||||||
|
}
|
||||||
|
if result == NickEnforcementOptional {
|
||||||
|
// enforcement was explicitly enabled neither in the config or by the user
|
||||||
|
result = NickEnforcementNone
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Given a nick, looks up the account that owns it and the method (none/timeout/strict)
|
// Given a nick, looks up the account that owns it and the method (none/timeout/strict)
|
||||||
// used to enforce ownership.
|
// used to enforce ownership.
|
||||||
func (am *AccountManager) EnforcementStatus(cfnick, skeleton string) (account string, method NickReservationMethod) {
|
func (am *AccountManager) EnforcementStatus(cfnick, skeleton string) (account string, method NickEnforcementMethod) {
|
||||||
config := am.server.Config()
|
config := am.server.Config()
|
||||||
if !config.Accounts.NickReservation.Enabled {
|
if !config.Accounts.NickReservation.Enabled {
|
||||||
return "", NickReservationNone
|
return "", NickEnforcementNone
|
||||||
}
|
}
|
||||||
|
|
||||||
am.RLock()
|
am.RLock()
|
||||||
defer am.RUnlock()
|
defer am.RUnlock()
|
||||||
|
|
||||||
// given an account, combine stored enforcement method with the config settings
|
finalEnforcementMethod := func(account_ string) (result NickEnforcementMethod) {
|
||||||
// to compute the actual enforcement method
|
storedMethod := am.accountToMethod[account_]
|
||||||
finalEnforcementMethod := func(account_ string) (result NickReservationMethod) {
|
return configuredEnforcementMethod(config, storedMethod)
|
||||||
result = am.accountToMethod[account_]
|
|
||||||
// if they don't have a custom setting, or customization is disabled, use the default
|
|
||||||
if result == NickReservationOptional || !config.Accounts.NickReservation.AllowCustomEnforcement {
|
|
||||||
result = config.Accounts.NickReservation.Method
|
|
||||||
}
|
|
||||||
if result == NickReservationOptional {
|
|
||||||
// enforcement was explicitly enabled neither in the config or by the user
|
|
||||||
result = NickReservationNone
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nickAccount := am.nickToAccount[cfnick]
|
nickAccount := am.nickToAccount[cfnick]
|
||||||
skelAccount := am.skeletonToAccount[skeleton]
|
skelAccount := am.skeletonToAccount[skeleton]
|
||||||
if nickAccount == "" && skelAccount == "" {
|
if nickAccount == "" && skelAccount == "" {
|
||||||
return "", NickReservationNone
|
return "", NickEnforcementNone
|
||||||
} else if nickAccount != "" && (skelAccount == nickAccount || skelAccount == "") {
|
} else if nickAccount != "" && (skelAccount == nickAccount || skelAccount == "") {
|
||||||
return nickAccount, finalEnforcementMethod(nickAccount)
|
return nickAccount, finalEnforcementMethod(nickAccount)
|
||||||
} else if skelAccount != "" && nickAccount == "" {
|
} else if skelAccount != "" && nickAccount == "" {
|
||||||
@ -220,75 +232,47 @@ func (am *AccountManager) EnforcementStatus(cfnick, skeleton string) (account st
|
|||||||
nickMethod := finalEnforcementMethod(nickAccount)
|
nickMethod := finalEnforcementMethod(nickAccount)
|
||||||
skelMethod := finalEnforcementMethod(skelAccount)
|
skelMethod := finalEnforcementMethod(skelAccount)
|
||||||
switch {
|
switch {
|
||||||
case skelMethod == NickReservationNone:
|
case skelMethod == NickEnforcementNone:
|
||||||
return nickAccount, nickMethod
|
return nickAccount, nickMethod
|
||||||
case nickMethod == NickReservationNone:
|
case nickMethod == NickEnforcementNone:
|
||||||
return skelAccount, skelMethod
|
return skelAccount, skelMethod
|
||||||
default:
|
default:
|
||||||
// nobody can use this nick
|
// nobody can use this nick
|
||||||
return "!", NickReservationStrict
|
return "!", NickEnforcementStrict
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) BouncerAllowed(account string, session *Session) bool {
|
|
||||||
// TODO stub
|
|
||||||
config := am.server.Config()
|
|
||||||
if !config.Accounts.Bouncer.Enabled {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if config.Accounts.Bouncer.AllowedByDefault {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return session != nil && session.capabilities.Has(caps.Bouncer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Looks up the enforcement method stored in the database for an account
|
|
||||||
// (typically you want EnforcementStatus instead, which respects the config)
|
|
||||||
func (am *AccountManager) getStoredEnforcementStatus(account string) string {
|
|
||||||
am.RLock()
|
|
||||||
defer am.RUnlock()
|
|
||||||
return nickReservationToString(am.accountToMethod[account])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets a custom enforcement method for an account and stores it in the database.
|
// Sets a custom enforcement method for an account and stores it in the database.
|
||||||
func (am *AccountManager) SetEnforcementStatus(account string, method NickReservationMethod) (err error) {
|
func (am *AccountManager) SetEnforcementStatus(account string, method NickEnforcementMethod) (finalSettings AccountSettings, err error) {
|
||||||
config := am.server.Config()
|
config := am.server.Config()
|
||||||
if !(config.Accounts.NickReservation.Enabled && config.Accounts.NickReservation.AllowCustomEnforcement) {
|
if !(config.Accounts.NickReservation.Enabled && config.Accounts.NickReservation.AllowCustomEnforcement) {
|
||||||
return errFeatureDisabled
|
err = errFeatureDisabled
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var serialized string
|
setter := func(in AccountSettings) (out AccountSettings, err error) {
|
||||||
if method == NickReservationOptional {
|
out = in
|
||||||
serialized = "" // normally this is "default", but we're going to delete the key
|
out.NickEnforcement = method
|
||||||
} else {
|
return out, nil
|
||||||
serialized = nickReservationToString(method)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
key := fmt.Sprintf(keyAccountEnforcement, account)
|
_, err = am.ModifyAccountSettings(account, setter)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// this update of the data plane is racey, but it's probably fine
|
||||||
am.Lock()
|
am.Lock()
|
||||||
defer am.Unlock()
|
defer am.Unlock()
|
||||||
|
|
||||||
currentMethod := am.accountToMethod[account]
|
if method == NickEnforcementOptional {
|
||||||
if method != currentMethod {
|
|
||||||
if method == NickReservationOptional {
|
|
||||||
delete(am.accountToMethod, account)
|
delete(am.accountToMethod, account)
|
||||||
} else {
|
} else {
|
||||||
am.accountToMethod[account] = method
|
am.accountToMethod[account] = method
|
||||||
}
|
}
|
||||||
|
|
||||||
return am.server.store.Update(func(tx *buntdb.Tx) (err error) {
|
|
||||||
if serialized != "" {
|
|
||||||
_, _, err = tx.Set(key, nickReservationToString(method), nil)
|
|
||||||
} else {
|
|
||||||
_, err = tx.Delete(key)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) AccountToClients(account string) (result []*Client) {
|
func (am *AccountManager) AccountToClients(account string) (result []*Client) {
|
||||||
@ -813,6 +797,12 @@ func (am *AccountManager) deserializeRawAccount(raw rawClientAccount) (result Cl
|
|||||||
// pretend they have no vhost and move on
|
// pretend they have no vhost and move on
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if raw.Settings != "" {
|
||||||
|
e := json.Unmarshal([]byte(raw.Settings), &result.Settings)
|
||||||
|
if e != nil {
|
||||||
|
am.server.logger.Warning("internal", "could not unmarshal settings for account", result.Name, e.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -825,6 +815,7 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
|||||||
callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
|
||||||
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
||||||
vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
|
vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
|
||||||
|
settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
||||||
|
|
||||||
_, e := tx.Get(accountKey)
|
_, e := tx.Get(accountKey)
|
||||||
if e == buntdb.ErrNotFound {
|
if e == buntdb.ErrNotFound {
|
||||||
@ -838,6 +829,7 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
|||||||
result.Callback, _ = tx.Get(callbackKey)
|
result.Callback, _ = tx.Get(callbackKey)
|
||||||
result.AdditionalNicks, _ = tx.Get(nicksKey)
|
result.AdditionalNicks, _ = tx.Get(nicksKey)
|
||||||
result.VHost, _ = tx.Get(vhostKey)
|
result.VHost, _ = tx.Get(vhostKey)
|
||||||
|
result.Settings, _ = tx.Get(settingsKey)
|
||||||
|
|
||||||
if _, e = tx.Get(verifiedKey); e == nil {
|
if _, e = tx.Get(verifiedKey); e == nil {
|
||||||
result.Verified = true
|
result.Verified = true
|
||||||
@ -861,7 +853,7 @@ func (am *AccountManager) Unregister(account string) error {
|
|||||||
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)
|
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
||||||
enforcementKey := fmt.Sprintf(keyAccountEnforcement, casefoldedAccount)
|
settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
||||||
vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
|
vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
|
||||||
vhostQueueKey := fmt.Sprintf(keyVHostQueueAcctToId, casefoldedAccount)
|
vhostQueueKey := fmt.Sprintf(keyVHostQueueAcctToId, casefoldedAccount)
|
||||||
channelsKey := fmt.Sprintf(keyAccountChannels, casefoldedAccount)
|
channelsKey := fmt.Sprintf(keyAccountChannels, casefoldedAccount)
|
||||||
@ -892,7 +884,7 @@ func (am *AccountManager) Unregister(account string) error {
|
|||||||
tx.Delete(registeredTimeKey)
|
tx.Delete(registeredTimeKey)
|
||||||
tx.Delete(callbackKey)
|
tx.Delete(callbackKey)
|
||||||
tx.Delete(verificationCodeKey)
|
tx.Delete(verificationCodeKey)
|
||||||
tx.Delete(enforcementKey)
|
tx.Delete(settingsKey)
|
||||||
rawNicks, _ = tx.Get(nicksKey)
|
rawNicks, _ = tx.Get(nicksKey)
|
||||||
tx.Delete(nicksKey)
|
tx.Delete(nicksKey)
|
||||||
credText, err = tx.Get(credentialsKey)
|
credText, err = tx.Get(credentialsKey)
|
||||||
@ -980,19 +972,13 @@ func (am *AccountManager) AuthenticateByCertFP(client *Client) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var account string
|
var account string
|
||||||
var rawAccount rawClientAccount
|
|
||||||
certFPKey := fmt.Sprintf(keyCertToAccount, client.certfp)
|
certFPKey := fmt.Sprintf(keyCertToAccount, client.certfp)
|
||||||
|
|
||||||
err := am.server.store.Update(func(tx *buntdb.Tx) error {
|
err := am.server.store.View(func(tx *buntdb.Tx) error {
|
||||||
var err error
|
|
||||||
account, _ = tx.Get(certFPKey)
|
account, _ = tx.Get(certFPKey)
|
||||||
if account == "" {
|
if account == "" {
|
||||||
return errAccountInvalidCredentials
|
return errAccountInvalidCredentials
|
||||||
}
|
}
|
||||||
rawAccount, err = am.loadRawAccount(tx, account)
|
|
||||||
if err != nil || !rawAccount.Verified {
|
|
||||||
return errAccountUnverified
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1001,14 +987,57 @@ func (am *AccountManager) AuthenticateByCertFP(client *Client) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ok, we found an account corresponding to their certificate
|
// ok, we found an account corresponding to their certificate
|
||||||
clientAccount, err := am.deserializeRawAccount(rawAccount)
|
clientAccount, err := am.LoadAccount(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
} else if !clientAccount.Verified {
|
||||||
|
return errAccountUnverified
|
||||||
}
|
}
|
||||||
am.Login(client, clientAccount)
|
am.Login(client, clientAccount)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type settingsMunger func(input AccountSettings) (output AccountSettings, err error)
|
||||||
|
|
||||||
|
func (am *AccountManager) ModifyAccountSettings(account string, munger settingsMunger) (newSettings AccountSettings, err error) {
|
||||||
|
casefoldedAccount, err := CasefoldName(account)
|
||||||
|
if err != nil {
|
||||||
|
return newSettings, errAccountDoesNotExist
|
||||||
|
}
|
||||||
|
// TODO implement this in general via a compare-and-swap API
|
||||||
|
accountData, err := am.LoadAccount(casefoldedAccount)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
} else if !accountData.Verified {
|
||||||
|
return newSettings, errAccountUnverified
|
||||||
|
}
|
||||||
|
newSettings, err = munger(accountData.Settings)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
text, err := json.Marshal(newSettings)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
key := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
||||||
|
serializedValue := string(text)
|
||||||
|
err = am.server.store.Update(func(tx *buntdb.Tx) (err error) {
|
||||||
|
_, _, err = tx.Set(key, serializedValue, nil)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
err = errAccountUpdateFailed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// success, push new settings into the client objects
|
||||||
|
am.Lock()
|
||||||
|
defer am.Unlock()
|
||||||
|
for _, client := range am.accountToClients[casefoldedAccount] {
|
||||||
|
client.SetAccountSettings(newSettings)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// represents someone's status in hostserv
|
// represents someone's status in hostserv
|
||||||
type VHostInfo struct {
|
type VHostInfo struct {
|
||||||
ApprovedVHost string
|
ApprovedVHost string
|
||||||
@ -1237,6 +1266,9 @@ func (am *AccountManager) Login(client *Client, account ClientAccount) {
|
|||||||
am.Lock()
|
am.Lock()
|
||||||
defer am.Unlock()
|
defer am.Unlock()
|
||||||
am.accountToClients[casefoldedAccount] = append(am.accountToClients[casefoldedAccount], client)
|
am.accountToClients[casefoldedAccount] = append(am.accountToClients[casefoldedAccount], client)
|
||||||
|
for _, client := range am.accountToClients[casefoldedAccount] {
|
||||||
|
client.SetAccountSettings(account.Settings)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) Logout(client *Client) {
|
func (am *AccountManager) Logout(client *Client) {
|
||||||
@ -1283,6 +1315,21 @@ type AccountCredentials struct {
|
|||||||
Certificate string // fingerprint
|
Certificate string // fingerprint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BouncerAllowedSetting int
|
||||||
|
|
||||||
|
const (
|
||||||
|
BouncerAllowedServerDefault BouncerAllowedSetting = iota
|
||||||
|
BouncerDisallowedByUser
|
||||||
|
BouncerAllowedByUser
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountSettings struct {
|
||||||
|
AutoreplayLines *int
|
||||||
|
NickEnforcement NickEnforcementMethod
|
||||||
|
AllowBouncer BouncerAllowedSetting
|
||||||
|
AutoreplayJoins bool
|
||||||
|
}
|
||||||
|
|
||||||
// ClientAccount represents a user account.
|
// ClientAccount represents a user account.
|
||||||
type ClientAccount struct {
|
type ClientAccount struct {
|
||||||
// Name of the account.
|
// Name of the account.
|
||||||
@ -1293,6 +1340,7 @@ type ClientAccount struct {
|
|||||||
Verified bool
|
Verified bool
|
||||||
AdditionalNicks []string
|
AdditionalNicks []string
|
||||||
VHost VHostInfo
|
VHost VHostInfo
|
||||||
|
Settings AccountSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
// convenience for passing around raw serialized account data
|
// convenience for passing around raw serialized account data
|
||||||
@ -1304,6 +1352,7 @@ type rawClientAccount struct {
|
|||||||
Verified bool
|
Verified bool
|
||||||
AdditionalNicks string
|
AdditionalNicks string
|
||||||
VHost string
|
VHost string
|
||||||
|
Settings string
|
||||||
}
|
}
|
||||||
|
|
||||||
// logoutOfAccount logs the client out of their current account.
|
// logoutOfAccount logs the client out of their current account.
|
||||||
|
@ -114,9 +114,3 @@ func (s *Set) String(version Version, values *Values) string {
|
|||||||
|
|
||||||
return strings.Join(strs, " ")
|
return strings.Join(strs, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns whether we should send `znc.in/self-message`-style echo messages
|
|
||||||
// to sessions other than that which originated the message
|
|
||||||
func (capabs *Set) SelfMessagesEnabled() bool {
|
|
||||||
return capabs.Has(EchoMessage) || capabs.Has(ZNCSelfMessage)
|
|
||||||
}
|
|
||||||
|
@ -620,7 +620,17 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
|
|||||||
// TODO #259 can be implemented as Flush(false) (i.e., nonblocking) while holding joinPartMutex
|
// TODO #259 can be implemented as Flush(false) (i.e., nonblocking) while holding joinPartMutex
|
||||||
rb.Flush(true)
|
rb.Flush(true)
|
||||||
|
|
||||||
replayLimit := channel.server.Config().History.AutoreplayOnJoin
|
var replayLimit int
|
||||||
|
customReplayLimit := client.AccountSettings().AutoreplayLines
|
||||||
|
if customReplayLimit != nil {
|
||||||
|
replayLimit = *customReplayLimit
|
||||||
|
maxLimit := channel.server.Config().History.ChathistoryMax
|
||||||
|
if maxLimit < replayLimit {
|
||||||
|
replayLimit = maxLimit
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
replayLimit = channel.server.Config().History.AutoreplayOnJoin
|
||||||
|
}
|
||||||
if 0 < replayLimit {
|
if 0 < replayLimit {
|
||||||
// TODO don't replay the client's own JOIN line?
|
// TODO don't replay the client's own JOIN line?
|
||||||
items := channel.history.Latest(replayLimit)
|
items := channel.history.Latest(replayLimit)
|
||||||
@ -782,6 +792,7 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
|
|||||||
client := rb.target
|
client := rb.target
|
||||||
eventPlayback := rb.session.capabilities.Has(caps.EventPlayback)
|
eventPlayback := rb.session.capabilities.Has(caps.EventPlayback)
|
||||||
extendedJoin := rb.session.capabilities.Has(caps.ExtendedJoin)
|
extendedJoin := rb.session.capabilities.Has(caps.ExtendedJoin)
|
||||||
|
playJoinsAsPrivmsg := (!autoreplay || client.AccountSettings().AutoreplayJoins)
|
||||||
|
|
||||||
if len(items) == 0 {
|
if len(items) == 0 {
|
||||||
return
|
return
|
||||||
@ -808,7 +819,7 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
|
|||||||
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "HEVENT", "JOIN", chname)
|
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "HEVENT", "JOIN", chname)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if autoreplay {
|
if !playJoinsAsPrivmsg {
|
||||||
continue // #474
|
continue // #474
|
||||||
}
|
}
|
||||||
var message string
|
var message string
|
||||||
@ -823,7 +834,7 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
|
|||||||
if eventPlayback {
|
if eventPlayback {
|
||||||
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "HEVENT", "PART", chname, item.Message.Message)
|
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "HEVENT", "PART", chname, item.Message.Message)
|
||||||
} else {
|
} else {
|
||||||
if autoreplay {
|
if !playJoinsAsPrivmsg {
|
||||||
continue // #474
|
continue // #474
|
||||||
}
|
}
|
||||||
message := fmt.Sprintf(client.t("%[1]s left the channel (%[2]s)"), nick, item.Message.Message)
|
message := fmt.Sprintf(client.t("%[1]s left the channel (%[2]s)"), nick, item.Message.Message)
|
||||||
@ -840,7 +851,7 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
|
|||||||
if eventPlayback {
|
if eventPlayback {
|
||||||
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "HEVENT", "QUIT", item.Message.Message)
|
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "HEVENT", "QUIT", item.Message.Message)
|
||||||
} else {
|
} else {
|
||||||
if autoreplay {
|
if !playJoinsAsPrivmsg {
|
||||||
continue // #474
|
continue // #474
|
||||||
}
|
}
|
||||||
message := fmt.Sprintf(client.t("%[1]s quit (%[2]s)"), nick, item.Message.Message)
|
message := fmt.Sprintf(client.t("%[1]s quit (%[2]s)"), nick, item.Message.Message)
|
||||||
@ -989,7 +1000,7 @@ func (channel *Channel) SendSplitMessage(command string, minPrefixMode modes.Mod
|
|||||||
}
|
}
|
||||||
// send echo-message to other connected sessions
|
// send echo-message to other connected sessions
|
||||||
for _, session := range client.Sessions() {
|
for _, session := range client.Sessions() {
|
||||||
if session == rb.session || !session.capabilities.SelfMessagesEnabled() {
|
if session == rb.session {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var tagsToUse map[string]string
|
var tagsToUse map[string]string
|
||||||
@ -998,7 +1009,7 @@ func (channel *Channel) SendSplitMessage(command string, minPrefixMode modes.Mod
|
|||||||
}
|
}
|
||||||
if histType == history.Tagmsg && session.capabilities.Has(caps.MessageTags) {
|
if histType == history.Tagmsg && session.capabilities.Has(caps.MessageTags) {
|
||||||
session.sendFromClientInternal(false, message.Time, message.Msgid, nickmask, account, tagsToUse, command, chname)
|
session.sendFromClientInternal(false, message.Time, message.Msgid, nickmask, account, tagsToUse, command, chname)
|
||||||
} else {
|
} else if histType != history.Tagmsg {
|
||||||
session.sendSplitMsgFromClientInternal(false, nickmask, account, tagsToUse, command, chname, message)
|
session.sendSplitMsgFromClientInternal(false, nickmask, account, tagsToUse, command, chname, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ referenced by their registered account names, not their nicknames.`,
|
|||||||
|
|
||||||
// csNotice sends the client a notice from ChanServ
|
// csNotice sends the client a notice from ChanServ
|
||||||
func csNotice(rb *ResponseBuffer, text string) {
|
func csNotice(rb *ResponseBuffer, text string) {
|
||||||
rb.Add(nil, "ChanServ", "NOTICE", rb.target.Nick(), text)
|
rb.Add(nil, "ChanServ!ChanServ@localhost", "NOTICE", rb.target.Nick(), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func csAmodeHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
func csAmodeHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
|
@ -48,6 +48,7 @@ type ResumeDetails struct {
|
|||||||
type Client struct {
|
type Client struct {
|
||||||
account string
|
account string
|
||||||
accountName string // display name of the account: uncasefolded, '*' if not logged in
|
accountName string // display name of the account: uncasefolded, '*' if not logged in
|
||||||
|
accountSettings AccountSettings
|
||||||
atime time.Time
|
atime time.Time
|
||||||
away bool
|
away bool
|
||||||
awayMessage string
|
awayMessage string
|
||||||
@ -600,7 +601,8 @@ func (client *Client) tryResumeChannels() {
|
|||||||
|
|
||||||
func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.Item, complete bool) {
|
func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.Item, complete bool) {
|
||||||
var batchID string
|
var batchID string
|
||||||
nick := client.Nick()
|
details := client.Details()
|
||||||
|
nick := details.nick
|
||||||
if 0 < len(items) {
|
if 0 < len(items) {
|
||||||
batchID = rb.StartNestedHistoryBatch(nick)
|
batchID = rb.StartNestedHistoryBatch(nick)
|
||||||
}
|
}
|
||||||
@ -626,7 +628,14 @@ func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.I
|
|||||||
if allowTags {
|
if allowTags {
|
||||||
tags = item.Tags
|
tags = item.Tags
|
||||||
}
|
}
|
||||||
|
if item.Params[0] == "" {
|
||||||
|
// this message was sent *to* the client from another nick
|
||||||
rb.AddSplitMessageFromClient(item.Nick, item.AccountName, tags, command, nick, item.Message)
|
rb.AddSplitMessageFromClient(item.Nick, item.AccountName, tags, command, nick, item.Message)
|
||||||
|
} else {
|
||||||
|
// this message was sent *from* the client to another nick; the target is item.Params[0]
|
||||||
|
// substitute the client's current nickmask in case they changed nick
|
||||||
|
rb.AddSplitMessageFromClient(details.nickMask, item.AccountName, tags, command, item.Params[0], item.Message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rb.EndNestedBatch(batchID)
|
rb.EndNestedBatch(batchID)
|
||||||
|
@ -145,7 +145,20 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
|
|||||||
|
|
||||||
reservedAccount, method := client.server.accounts.EnforcementStatus(newcfnick, newSkeleton)
|
reservedAccount, method := client.server.accounts.EnforcementStatus(newcfnick, newSkeleton)
|
||||||
account := client.Account()
|
account := client.Account()
|
||||||
bouncerAllowed := client.server.accounts.BouncerAllowed(account, session)
|
config := client.server.Config()
|
||||||
|
var bouncerAllowed bool
|
||||||
|
if config.Accounts.Bouncer.Enabled {
|
||||||
|
if session != nil && session.capabilities.Has(caps.Bouncer) {
|
||||||
|
bouncerAllowed = true
|
||||||
|
} else {
|
||||||
|
settings := client.AccountSettings()
|
||||||
|
if config.Accounts.Bouncer.AllowedByDefault && settings.AllowBouncer != BouncerDisallowedByUser {
|
||||||
|
bouncerAllowed = true
|
||||||
|
} else if settings.AllowBouncer == BouncerAllowedByUser {
|
||||||
|
bouncerAllowed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
clients.Lock()
|
clients.Lock()
|
||||||
defer clients.Unlock()
|
defer clients.Unlock()
|
||||||
@ -168,7 +181,7 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
|
|||||||
if skeletonHolder != nil && skeletonHolder != client {
|
if skeletonHolder != nil && skeletonHolder != client {
|
||||||
return errNicknameInUse
|
return errNicknameInUse
|
||||||
}
|
}
|
||||||
if method == NickReservationStrict && reservedAccount != "" && reservedAccount != account {
|
if method == NickEnforcementStrict && reservedAccount != "" && reservedAccount != account {
|
||||||
return errNicknameReserved
|
return errNicknameReserved
|
||||||
}
|
}
|
||||||
clients.removeInternal(client)
|
clients.removeInternal(client)
|
||||||
|
@ -112,63 +112,63 @@ type VHostConfig struct {
|
|||||||
} `yaml:"user-requests"`
|
} `yaml:"user-requests"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NickReservationMethod int
|
type NickEnforcementMethod int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// NickReservationOptional is the zero value; it serializes to
|
// NickEnforcementOptional is the zero value; it serializes to
|
||||||
// "optional" in the yaml config, and "default" as an arg to `NS ENFORCE`.
|
// "optional" in the yaml config, and "default" as an arg to `NS ENFORCE`.
|
||||||
// in both cases, it means "defer to the other source of truth", i.e.,
|
// in both cases, it means "defer to the other source of truth", i.e.,
|
||||||
// in the config, defer to the user's custom setting, and as a custom setting,
|
// in the config, defer to the user's custom setting, and as a custom setting,
|
||||||
// defer to the default in the config. if both are NickReservationOptional then
|
// defer to the default in the config. if both are NickEnforcementOptional then
|
||||||
// there is no enforcement.
|
// there is no enforcement.
|
||||||
NickReservationOptional NickReservationMethod = iota
|
// XXX: these are serialized as numbers in the database, so beware of collisions
|
||||||
NickReservationNone
|
// when refactoring (any numbers currently in use must keep their meanings, or
|
||||||
NickReservationWithTimeout
|
// else be fixed up by a schema change)
|
||||||
NickReservationStrict
|
NickEnforcementOptional NickEnforcementMethod = iota
|
||||||
|
NickEnforcementNone
|
||||||
|
NickEnforcementWithTimeout
|
||||||
|
NickEnforcementStrict
|
||||||
)
|
)
|
||||||
|
|
||||||
func nickReservationToString(method NickReservationMethod) string {
|
func nickReservationToString(method NickEnforcementMethod) string {
|
||||||
switch method {
|
switch method {
|
||||||
case NickReservationOptional:
|
case NickEnforcementOptional:
|
||||||
return "default"
|
return "default"
|
||||||
case NickReservationNone:
|
case NickEnforcementNone:
|
||||||
return "none"
|
return "none"
|
||||||
case NickReservationWithTimeout:
|
case NickEnforcementWithTimeout:
|
||||||
return "timeout"
|
return "timeout"
|
||||||
case NickReservationStrict:
|
case NickEnforcementStrict:
|
||||||
return "strict"
|
return "strict"
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func nickReservationFromString(method string) (NickReservationMethod, error) {
|
func nickReservationFromString(method string) (NickEnforcementMethod, error) {
|
||||||
switch method {
|
switch strings.ToLower(method) {
|
||||||
case "default":
|
case "default":
|
||||||
return NickReservationOptional, nil
|
return NickEnforcementOptional, nil
|
||||||
case "optional":
|
case "optional":
|
||||||
return NickReservationOptional, nil
|
return NickEnforcementOptional, nil
|
||||||
case "none":
|
case "none":
|
||||||
return NickReservationNone, nil
|
return NickEnforcementNone, nil
|
||||||
case "timeout":
|
case "timeout":
|
||||||
return NickReservationWithTimeout, nil
|
return NickEnforcementWithTimeout, nil
|
||||||
case "strict":
|
case "strict":
|
||||||
return NickReservationStrict, nil
|
return NickEnforcementStrict, nil
|
||||||
default:
|
default:
|
||||||
return NickReservationOptional, fmt.Errorf("invalid nick-reservation.method value: %s", method)
|
return NickEnforcementOptional, fmt.Errorf("invalid nick-reservation.method value: %s", method)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nr *NickReservationMethod) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
func (nr *NickEnforcementMethod) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
var orig, raw string
|
var orig string
|
||||||
var err error
|
var err error
|
||||||
if err = unmarshal(&orig); err != nil {
|
if err = unmarshal(&orig); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if raw, err = Casefold(orig); err != nil {
|
method, err := nickReservationFromString(orig)
|
||||||
return err
|
|
||||||
}
|
|
||||||
method, err := nickReservationFromString(raw)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
*nr = method
|
*nr = method
|
||||||
}
|
}
|
||||||
@ -178,7 +178,7 @@ func (nr *NickReservationMethod) UnmarshalYAML(unmarshal func(interface{}) error
|
|||||||
type NickReservationConfig struct {
|
type NickReservationConfig struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
AdditionalNickLimit int `yaml:"additional-nick-limit"`
|
AdditionalNickLimit int `yaml:"additional-nick-limit"`
|
||||||
Method NickReservationMethod
|
Method NickEnforcementMethod
|
||||||
AllowCustomEnforcement bool `yaml:"allow-custom-enforcement"`
|
AllowCustomEnforcement bool `yaml:"allow-custom-enforcement"`
|
||||||
RenameTimeout time.Duration `yaml:"rename-timeout"`
|
RenameTimeout time.Duration `yaml:"rename-timeout"`
|
||||||
RenamePrefix string `yaml:"rename-prefix"`
|
RenamePrefix string `yaml:"rename-prefix"`
|
||||||
|
@ -22,7 +22,7 @@ const (
|
|||||||
// 'version' of the database schema
|
// 'version' of the database schema
|
||||||
keySchemaVersion = "db.version"
|
keySchemaVersion = "db.version"
|
||||||
// latest schema of the db
|
// latest schema of the db
|
||||||
latestDbSchema = "5"
|
latestDbSchema = "6"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SchemaChanger func(*Config, *buntdb.Tx) error
|
type SchemaChanger func(*Config, *buntdb.Tx) error
|
||||||
@ -409,6 +409,37 @@ func schemaChangeV4ToV5(config *Config, tx *buntdb.Tx) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// custom nick enforcement was a separate db key, now it's part of settings
|
||||||
|
func schemaChangeV5ToV6(config *Config, tx *buntdb.Tx) error {
|
||||||
|
accountToEnforcement := make(map[string]NickEnforcementMethod)
|
||||||
|
prefix := "account.customenforcement "
|
||||||
|
tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
|
||||||
|
if !strings.HasPrefix(key, prefix) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
account := strings.TrimPrefix(key, prefix)
|
||||||
|
method, err := nickReservationFromString(value)
|
||||||
|
if err == nil {
|
||||||
|
accountToEnforcement[account] = method
|
||||||
|
} else {
|
||||||
|
log.Printf("skipping corrupt custom enforcement value for %s\n", account)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
for account, method := range accountToEnforcement {
|
||||||
|
var settings AccountSettings
|
||||||
|
settings.NickEnforcement = method
|
||||||
|
text, err := json.Marshal(settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tx.Delete(prefix + account)
|
||||||
|
tx.Set(fmt.Sprintf("account.settings %s", account), string(text), nil)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
allChanges := []SchemaChange{
|
allChanges := []SchemaChange{
|
||||||
{
|
{
|
||||||
@ -431,6 +462,11 @@ func init() {
|
|||||||
TargetVersion: "5",
|
TargetVersion: "5",
|
||||||
Changer: schemaChangeV4ToV5,
|
Changer: schemaChangeV4ToV5,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
InitialVersion: "5",
|
||||||
|
TargetVersion: "6",
|
||||||
|
Changer: schemaChangeV5ToV6,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// build the index
|
// build the index
|
||||||
|
@ -5,7 +5,10 @@
|
|||||||
|
|
||||||
package irc
|
package irc
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/oragono/oragono/irc/utils"
|
||||||
|
)
|
||||||
|
|
||||||
// Runtime Errors
|
// Runtime Errors
|
||||||
var (
|
var (
|
||||||
@ -19,10 +22,10 @@ var (
|
|||||||
errAccountNickReservationFailed = errors.New("Could not (un)reserve nick")
|
errAccountNickReservationFailed = errors.New("Could not (un)reserve nick")
|
||||||
errAccountNotLoggedIn = errors.New("You're not logged into an account")
|
errAccountNotLoggedIn = errors.New("You're not logged into an account")
|
||||||
errAccountTooManyNicks = errors.New("Account has too many reserved nicks")
|
errAccountTooManyNicks = errors.New("Account has too many reserved nicks")
|
||||||
errAccountUnverified = errors.New("Account is not yet verified")
|
errAccountUnverified = errors.New(`Account is not yet verified`)
|
||||||
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")
|
||||||
errAccountUpdateFailed = errors.New("Error while updating your account information")
|
errAccountUpdateFailed = errors.New(`Error while updating your account information`)
|
||||||
errAccountMustHoldNick = errors.New(`You must hold that nickname in order to register it`)
|
errAccountMustHoldNick = errors.New(`You must hold that nickname in order to register it`)
|
||||||
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 for your certificate fingerprint`)
|
errCertfpAlreadyExists = errors.New(`An account already exists for your certificate fingerprint`)
|
||||||
@ -40,7 +43,7 @@ var (
|
|||||||
errInvalidUsername = errors.New("Invalid username")
|
errInvalidUsername = errors.New("Invalid username")
|
||||||
errFeatureDisabled = errors.New(`That feature is disabled`)
|
errFeatureDisabled = errors.New(`That feature is disabled`)
|
||||||
errBanned = errors.New("IP or nickmask banned")
|
errBanned = errors.New("IP or nickmask banned")
|
||||||
errInvalidParams = errors.New("Invalid parameters")
|
errInvalidParams = utils.ErrInvalidParams
|
||||||
)
|
)
|
||||||
|
|
||||||
// Socket Errors
|
// Socket Errors
|
||||||
|
@ -275,6 +275,19 @@ func (client *Client) SetAccountName(account string) (changed bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (client *Client) AccountSettings() (result AccountSettings) {
|
||||||
|
client.stateMutex.RLock()
|
||||||
|
result = client.accountSettings
|
||||||
|
client.stateMutex.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *Client) SetAccountSettings(settings AccountSettings) {
|
||||||
|
client.stateMutex.Lock()
|
||||||
|
client.accountSettings = settings
|
||||||
|
client.stateMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
func (client *Client) Languages() (languages []string) {
|
func (client *Client) Languages() (languages []string) {
|
||||||
client.stateMutex.RLock()
|
client.stateMutex.RLock()
|
||||||
languages = client.languages
|
languages = client.languages
|
||||||
|
@ -2046,12 +2046,12 @@ func messageHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
|
|||||||
}
|
}
|
||||||
// an echo-message may need to go out to other client sessions:
|
// an echo-message may need to go out to other client sessions:
|
||||||
for _, session := range client.Sessions() {
|
for _, session := range client.Sessions() {
|
||||||
if session == rb.session || !rb.session.capabilities.SelfMessagesEnabled() {
|
if session == rb.session {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if histType == history.Tagmsg && rb.session.capabilities.Has(caps.MessageTags) {
|
if histType == history.Tagmsg && rb.session.capabilities.Has(caps.MessageTags) {
|
||||||
session.sendFromClientInternal(false, splitMsg.Time, splitMsg.Msgid, nickMaskString, accountName, clientOnlyTags, msg.Command, tnick)
|
session.sendFromClientInternal(false, splitMsg.Time, splitMsg.Msgid, nickMaskString, accountName, clientOnlyTags, msg.Command, tnick)
|
||||||
} else {
|
} else if histType != history.Tagmsg {
|
||||||
session.sendSplitMsgFromClientInternal(false, nickMaskString, accountName, clientOnlyTags, msg.Command, tnick, splitMsg)
|
session.sendSplitMsgFromClientInternal(false, nickMaskString, accountName, clientOnlyTags, msg.Command, tnick, splitMsg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2060,12 +2060,17 @@ func messageHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
|
|||||||
rb.Add(nil, server.name, RPL_AWAY, cnick, tnick, user.AwayMessage())
|
rb.Add(nil, server.name, RPL_AWAY, cnick, tnick, user.AwayMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
user.history.Add(history.Item{
|
item := history.Item{
|
||||||
Type: histType,
|
Type: histType,
|
||||||
Message: splitMsg,
|
Message: splitMsg,
|
||||||
Nick: nickMaskString,
|
Nick: nickMaskString,
|
||||||
AccountName: accountName,
|
AccountName: accountName,
|
||||||
})
|
}
|
||||||
|
// add to the target's history:
|
||||||
|
user.history.Add(item)
|
||||||
|
// add this to the client's history as well, recording the target:
|
||||||
|
item.Params[0] = tnick
|
||||||
|
client.history.Add(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -131,7 +131,7 @@ for the rejection.`,
|
|||||||
|
|
||||||
// hsNotice sends the client a notice from HostServ
|
// hsNotice sends the client a notice from HostServ
|
||||||
func hsNotice(rb *ResponseBuffer, text string) {
|
func hsNotice(rb *ResponseBuffer, text string) {
|
||||||
rb.Add(nil, "HostServ", "NOTICE", rb.target.Nick(), text)
|
rb.Add(nil, "HostServ!HostServ@localhost", "NOTICE", rb.target.Nick(), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// hsNotifyChannel notifies the designated channel of new vhost activity
|
// hsNotifyChannel notifies the designated channel of new vhost activity
|
||||||
|
@ -198,7 +198,7 @@ func (nt *NickTimer) Initialize(client *Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config := &client.server.Config().Accounts.NickReservation
|
config := &client.server.Config().Accounts.NickReservation
|
||||||
enabled := config.Enabled && (config.Method == NickReservationWithTimeout || config.AllowCustomEnforcement)
|
enabled := config.Enabled && (config.Method == NickEnforcementWithTimeout || config.AllowCustomEnforcement)
|
||||||
|
|
||||||
nt.Lock()
|
nt.Lock()
|
||||||
defer nt.Unlock()
|
defer nt.Unlock()
|
||||||
@ -235,7 +235,7 @@ func (nt *NickTimer) Touch(rb *ResponseBuffer) {
|
|||||||
cfnick, skeleton := nt.client.uniqueIdentifiers()
|
cfnick, skeleton := nt.client.uniqueIdentifiers()
|
||||||
account := nt.client.Account()
|
account := nt.client.Account()
|
||||||
accountForNick, method := nt.client.server.accounts.EnforcementStatus(cfnick, skeleton)
|
accountForNick, method := nt.client.server.accounts.EnforcementStatus(cfnick, skeleton)
|
||||||
enforceTimeout := method == NickReservationWithTimeout
|
enforceTimeout := method == NickEnforcementWithTimeout
|
||||||
|
|
||||||
var shouldWarn, shouldRename bool
|
var shouldWarn, shouldRename bool
|
||||||
|
|
||||||
@ -258,7 +258,7 @@ func (nt *NickTimer) Touch(rb *ResponseBuffer) {
|
|||||||
if enforceTimeout && delinquent && (accountChanged || nt.timer == nil) {
|
if enforceTimeout && delinquent && (accountChanged || nt.timer == nil) {
|
||||||
nt.timer = time.AfterFunc(nt.timeout, nt.processTimeout)
|
nt.timer = time.AfterFunc(nt.timeout, nt.processTimeout)
|
||||||
shouldWarn = true
|
shouldWarn = true
|
||||||
} else if method == NickReservationStrict && delinquent {
|
} else if method == NickEnforcementStrict && delinquent {
|
||||||
shouldRename = true // this can happen if reservation was enabled by rehash
|
shouldRename = true // this can happen if reservation was enabled by rehash
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
277
irc/nickserv.go
277
irc/nickserv.go
@ -5,11 +5,14 @@ package irc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/goshuirc/irc-go/ircfmt"
|
"github.com/goshuirc/irc-go/ircfmt"
|
||||||
|
|
||||||
"github.com/oragono/oragono/irc/modes"
|
"github.com/oragono/oragono/irc/modes"
|
||||||
|
"github.com/oragono/oragono/irc/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// "enabled" callbacks for specific nickserv commands
|
// "enabled" callbacks for specific nickserv commands
|
||||||
@ -25,10 +28,6 @@ func servCmdRequiresNickRes(config *Config) bool {
|
|||||||
return config.Accounts.AuthenticationEnabled && config.Accounts.NickReservation.Enabled
|
return config.Accounts.AuthenticationEnabled && config.Accounts.NickReservation.Enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
func nsEnforceEnabled(config *Config) bool {
|
|
||||||
return servCmdRequiresNickRes(config) && config.Accounts.NickReservation.AllowCustomEnforcement
|
|
||||||
}
|
|
||||||
|
|
||||||
func servCmdRequiresBouncerEnabled(config *Config) bool {
|
func servCmdRequiresBouncerEnabled(config *Config) bool {
|
||||||
return config.Accounts.Bouncer.Enabled
|
return config.Accounts.Bouncer.Enabled
|
||||||
}
|
}
|
||||||
@ -61,20 +60,14 @@ DROP de-links the given (or your current) nickname from your user account.`,
|
|||||||
authRequired: true,
|
authRequired: true,
|
||||||
},
|
},
|
||||||
"enforce": {
|
"enforce": {
|
||||||
|
hidden: true,
|
||||||
handler: nsEnforceHandler,
|
handler: nsEnforceHandler,
|
||||||
help: `Syntax: $bENFORCE [method]$b
|
help: `Syntax: $bENFORCE [method]$b
|
||||||
|
|
||||||
ENFORCE lets you specify a custom enforcement mechanism for your registered
|
ENFORCE is an alias for $bGET enforce$b and $bSET enforce$b. See the help
|
||||||
nicknames. Your options are:
|
entry for $bSET$b for more information.`,
|
||||||
1. 'none' [no enforcement, overriding the server default]
|
|
||||||
2. 'timeout' [anyone using the nick must authenticate before a deadline,
|
|
||||||
or else they will be renamed]
|
|
||||||
3. 'strict' [you must already be authenticated to use the nick]
|
|
||||||
4. 'default' [use the server default]
|
|
||||||
With no arguments, queries your current enforcement status.`,
|
|
||||||
helpShort: `$bENFORCE$b lets you change how your nicknames are reserved.`,
|
|
||||||
authRequired: true,
|
authRequired: true,
|
||||||
enabled: nsEnforceEnabled,
|
enabled: servCmdRequiresAccreg,
|
||||||
},
|
},
|
||||||
"ghost": {
|
"ghost": {
|
||||||
handler: nsGhostHandler,
|
handler: nsGhostHandler,
|
||||||
@ -194,12 +187,246 @@ password by supplying their username and then the desired password.`,
|
|||||||
enabled: servCmdRequiresAuthEnabled,
|
enabled: servCmdRequiresAuthEnabled,
|
||||||
minParams: 2,
|
minParams: 2,
|
||||||
},
|
},
|
||||||
|
"get": {
|
||||||
|
handler: nsGetHandler,
|
||||||
|
help: `Syntax: $bGET <setting>$b
|
||||||
|
|
||||||
|
GET queries the current values of your account settings. For more information
|
||||||
|
on the settings and their possible values, see HELP SET.`,
|
||||||
|
helpShort: `$bGET$b queries the current values of your account settings`,
|
||||||
|
authRequired: true,
|
||||||
|
enabled: servCmdRequiresAccreg,
|
||||||
|
minParams: 1,
|
||||||
|
},
|
||||||
|
"saget": {
|
||||||
|
handler: nsGetHandler,
|
||||||
|
help: `Syntax: $bSAGET <account> <setting>$b
|
||||||
|
|
||||||
|
SAGET queries the values of someone else's account settings. For more
|
||||||
|
information on the settings and their possible values, see HELP SET.`,
|
||||||
|
helpShort: `$bSAGET$b queries the current values of another user's account settings`,
|
||||||
|
enabled: servCmdRequiresAccreg,
|
||||||
|
minParams: 2,
|
||||||
|
capabs: []string{"accreg"},
|
||||||
|
},
|
||||||
|
"set": {
|
||||||
|
handler: nsSetHandler,
|
||||||
|
helpShort: `$bSET$b modifies your account settings`,
|
||||||
|
// these are broken out as separate strings so they can be translated separately
|
||||||
|
helpStrings: []string{
|
||||||
|
`Syntax $bSET <setting> <value>$b
|
||||||
|
|
||||||
|
Set modifies your account settings. The following settings are available:`,
|
||||||
|
|
||||||
|
`$bENFORCE$b
|
||||||
|
'enforce' lets you specify a custom enforcement mechanism for your registered
|
||||||
|
nicknames. Your options are:
|
||||||
|
1. 'none' [no enforcement, overriding the server default]
|
||||||
|
2. 'timeout' [anyone using the nick must authenticate before a deadline,
|
||||||
|
or else they will be renamed]
|
||||||
|
3. 'strict' [you must already be authenticated to use the nick]
|
||||||
|
4. 'default' [use the server default]`,
|
||||||
|
|
||||||
|
`$bBOUNCER$b
|
||||||
|
If 'bouncer' is enabled and you are already logged in and using a nick, a
|
||||||
|
second client of yours that authenticates with SASL and requests the same nick
|
||||||
|
is allowed to attach to the nick as well (this is comparable to the behavior
|
||||||
|
of IRC "bouncers" like ZNC). Your options are 'on' (allow this behavior),
|
||||||
|
'off' (disallow it), and 'default' (use the server default value).`,
|
||||||
|
|
||||||
|
`$bAUTOREPLAY-LINES$b
|
||||||
|
'autoreplay-lines' controls the number of lines of channel history that will
|
||||||
|
be replayed to you automatically when joining a channel. Your options are any
|
||||||
|
positive number, 0 to disable the feature, and 'default' to use the server
|
||||||
|
default.`,
|
||||||
|
|
||||||
|
`$bAUTOREPLAY-JOINS$b
|
||||||
|
'autoreplay-joins' controls whether autoreplayed channel history will include
|
||||||
|
lines for join and part. This provides more information about the context of
|
||||||
|
messages, but may be spammy. Your options are 'on' and 'off'.`,
|
||||||
|
},
|
||||||
|
authRequired: true,
|
||||||
|
enabled: servCmdRequiresAccreg,
|
||||||
|
minParams: 2,
|
||||||
|
},
|
||||||
|
"saset": {
|
||||||
|
handler: nsSetHandler,
|
||||||
|
help: `Syntax: $bSASET <account> <setting> <value>$b
|
||||||
|
|
||||||
|
SASET modifies the values of someone else's account settings. For more
|
||||||
|
information on the settings and their possible values, see HELP SET.`,
|
||||||
|
helpShort: `$bSASET$b modifies another user's account settings`,
|
||||||
|
enabled: servCmdRequiresAccreg,
|
||||||
|
minParams: 3,
|
||||||
|
capabs: []string{"accreg"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// nsNotice sends the client a notice from NickServ
|
// nsNotice sends the client a notice from NickServ
|
||||||
func nsNotice(rb *ResponseBuffer, text string) {
|
func nsNotice(rb *ResponseBuffer, text string) {
|
||||||
rb.Add(nil, "NickServ", "NOTICE", rb.target.Nick(), text)
|
// XXX i can't figure out how to use OragonoServices[servicename].prefix here
|
||||||
|
// without creating a compile-time initialization loop
|
||||||
|
rb.Add(nil, "NickServ!NickServ@localhost", "NOTICE", rb.target.Nick(), text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nsGetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
|
var account string
|
||||||
|
if command == "saget" {
|
||||||
|
account = params[0]
|
||||||
|
params = params[1:]
|
||||||
|
} else {
|
||||||
|
account = client.Account()
|
||||||
|
}
|
||||||
|
|
||||||
|
accountData, err := server.accounts.LoadAccount(account)
|
||||||
|
if err == errAccountDoesNotExist {
|
||||||
|
nsNotice(rb, client.t("No such account"))
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
nsNotice(rb, client.t("Error loading account data"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
displaySetting(params[0], accountData.Settings, client, rb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func displaySetting(settingName string, settings AccountSettings, client *Client, rb *ResponseBuffer) {
|
||||||
|
config := client.server.Config()
|
||||||
|
switch strings.ToLower(settingName) {
|
||||||
|
case "enforce":
|
||||||
|
storedValue := settings.NickEnforcement
|
||||||
|
serializedStoredValue := nickReservationToString(storedValue)
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("Your stored nickname enforcement setting is: %s"), serializedStoredValue))
|
||||||
|
serializedActualValue := nickReservationToString(configuredEnforcementMethod(config, storedValue))
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("Given current server settings, your nickname is enforced with: %s"), serializedActualValue))
|
||||||
|
case "autoreplay-lines":
|
||||||
|
if settings.AutoreplayLines == nil {
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("You will receive the server default of %d lines of autoreplayed history"), config.History.AutoreplayOnJoin))
|
||||||
|
} else {
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("You will receive %d lines of autoreplayed history"), *settings.AutoreplayLines))
|
||||||
|
}
|
||||||
|
case "autoreplay-joins":
|
||||||
|
if settings.AutoreplayJoins {
|
||||||
|
nsNotice(rb, client.t("You will see JOINs and PARTs in autoreplayed history lines"))
|
||||||
|
} else {
|
||||||
|
nsNotice(rb, client.t("You will not see JOINs and PARTs in autoreplayed history lines"))
|
||||||
|
}
|
||||||
|
case "bouncer":
|
||||||
|
if !config.Accounts.Bouncer.Enabled {
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("This feature has been disabled by the server administrators")))
|
||||||
|
} else {
|
||||||
|
switch settings.AllowBouncer {
|
||||||
|
case BouncerAllowedServerDefault:
|
||||||
|
if config.Accounts.Bouncer.AllowedByDefault {
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("Bouncer functionality is currently enabled for your account, but you can opt out")))
|
||||||
|
} else {
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("Bouncer functionality is currently disabled for your account, but you can opt in")))
|
||||||
|
}
|
||||||
|
case BouncerDisallowedByUser:
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("Bouncer functionality is currently disabled for your account")))
|
||||||
|
case BouncerAllowedByUser:
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("Bouncer functionality is currently enabled for your account")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
nsNotice(rb, client.t("No such setting"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nsSetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
|
var account string
|
||||||
|
if command == "saset" {
|
||||||
|
account = params[0]
|
||||||
|
params = params[1:]
|
||||||
|
} else {
|
||||||
|
account = client.Account()
|
||||||
|
}
|
||||||
|
|
||||||
|
var munger settingsMunger
|
||||||
|
var finalSettings AccountSettings
|
||||||
|
var err error
|
||||||
|
switch strings.ToLower(params[0]) {
|
||||||
|
case "pass":
|
||||||
|
nsNotice(rb, client.t("To change a password, use the PASSWD command. For details, /msg NickServ HELP PASSWD"))
|
||||||
|
return
|
||||||
|
case "enforce":
|
||||||
|
var method NickEnforcementMethod
|
||||||
|
method, err = nickReservationFromString(params[1])
|
||||||
|
if err != nil {
|
||||||
|
err = errInvalidParams
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// updating enforcement settings is special-cased, because it requires
|
||||||
|
// an update to server.accounts.accountToMethod
|
||||||
|
finalSettings, err = server.accounts.SetEnforcementStatus(account, method)
|
||||||
|
if err == nil {
|
||||||
|
finalSettings.NickEnforcement = method // success
|
||||||
|
}
|
||||||
|
case "autoreplay-lines":
|
||||||
|
var newValue *int
|
||||||
|
if strings.ToLower(params[1]) != "default" {
|
||||||
|
val, err_ := strconv.Atoi(params[1])
|
||||||
|
if err_ != nil || val < 0 {
|
||||||
|
err = errInvalidParams
|
||||||
|
break
|
||||||
|
}
|
||||||
|
newValue = new(int)
|
||||||
|
*newValue = val
|
||||||
|
}
|
||||||
|
munger = func(in AccountSettings) (out AccountSettings, err error) {
|
||||||
|
out = in
|
||||||
|
out.AutoreplayLines = newValue
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "bouncer":
|
||||||
|
var newValue BouncerAllowedSetting
|
||||||
|
if strings.ToLower(params[1]) == "default" {
|
||||||
|
newValue = BouncerAllowedServerDefault
|
||||||
|
} else {
|
||||||
|
var enabled bool
|
||||||
|
enabled, err = utils.StringToBool(params[1])
|
||||||
|
if enabled {
|
||||||
|
newValue = BouncerAllowedByUser
|
||||||
|
} else {
|
||||||
|
newValue = BouncerDisallowedByUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
munger = func(in AccountSettings) (out AccountSettings, err error) {
|
||||||
|
out = in
|
||||||
|
out.AllowBouncer = newValue
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "autoreplay-joins":
|
||||||
|
var newValue bool
|
||||||
|
newValue, err = utils.StringToBool(params[1])
|
||||||
|
if err == nil {
|
||||||
|
munger = func(in AccountSettings) (out AccountSettings, err error) {
|
||||||
|
out = in
|
||||||
|
out.AutoreplayJoins = newValue
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = errInvalidParams
|
||||||
|
}
|
||||||
|
|
||||||
|
if munger != nil {
|
||||||
|
finalSettings, err = server.accounts.ModifyAccountSettings(account, munger)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
nsNotice(rb, client.t("Successfully changed your account settings"))
|
||||||
|
displaySetting(params[0], finalSettings, client, rb)
|
||||||
|
case errInvalidParams, errAccountDoesNotExist, errFeatureDisabled, errAccountUnverified, errAccountUpdateFailed:
|
||||||
|
nsNotice(rb, client.t(err.Error()))
|
||||||
|
default:
|
||||||
|
// unknown error
|
||||||
|
nsNotice(rb, client.t("An error occurred"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func nsDropHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
func nsDropHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
@ -550,7 +777,7 @@ func nsPasswdHandler(server *Server, client *Client, command string, params []st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
errorMessage = "Invalid parameters"
|
errorMessage = `Invalid parameters`
|
||||||
}
|
}
|
||||||
|
|
||||||
if errorMessage != "" {
|
if errorMessage != "" {
|
||||||
@ -568,22 +795,12 @@ func nsPasswdHandler(server *Server, client *Client, command string, params []st
|
|||||||
}
|
}
|
||||||
|
|
||||||
func nsEnforceHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
func nsEnforceHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
|
newParams := []string{"enforce"}
|
||||||
if len(params) == 0 {
|
if len(params) == 0 {
|
||||||
status := server.accounts.getStoredEnforcementStatus(client.Account())
|
nsGetHandler(server, client, "get", newParams, rb)
|
||||||
nsNotice(rb, fmt.Sprintf(client.t("Your current nickname enforcement is: %s"), status))
|
|
||||||
} else {
|
} else {
|
||||||
method, err := nickReservationFromString(params[0])
|
newParams = append(newParams, params[0])
|
||||||
if err != nil {
|
nsSetHandler(server, client, "set", newParams, rb)
|
||||||
nsNotice(rb, client.t("Invalid parameters"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = server.accounts.SetEnforcementStatus(client.Account(), method)
|
|
||||||
if err == nil {
|
|
||||||
nsNotice(rb, client.t("Enforcement method set"))
|
|
||||||
} else {
|
|
||||||
server.logger.Error("internal", "couldn't store NS ENFORCE data", err.Error())
|
|
||||||
nsNotice(rb, client.t("An error occurred"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ type resumeTokenPair struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ResumeManager struct {
|
type ResumeManager struct {
|
||||||
sync.RWMutex // level 2
|
sync.Mutex // level 2
|
||||||
|
|
||||||
resumeIDtoCreds map[string]resumeTokenPair
|
resumeIDtoCreds map[string]resumeTokenPair
|
||||||
server *Server
|
server *Server
|
||||||
@ -59,8 +59,8 @@ func (rm *ResumeManager) VerifyToken(token string) (client *Client) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rm.RLock()
|
rm.Lock()
|
||||||
defer rm.RUnlock()
|
defer rm.Unlock()
|
||||||
|
|
||||||
id := token[:utils.SecretTokenLength]
|
id := token[:utils.SecretTokenLength]
|
||||||
pair, ok := rm.resumeIDtoCreds[id]
|
pair, ok := rm.resumeIDtoCreds[id]
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
type ircService struct {
|
type ircService struct {
|
||||||
Name string
|
Name string
|
||||||
ShortName string
|
ShortName string
|
||||||
|
prefix string
|
||||||
CommandAliases []string
|
CommandAliases []string
|
||||||
Commands map[string]*serviceCommand
|
Commands map[string]*serviceCommand
|
||||||
HelpBanner string
|
HelpBanner string
|
||||||
@ -29,8 +30,10 @@ type serviceCommand struct {
|
|||||||
capabs []string // oper capabs the given user has to have to access this command
|
capabs []string // oper capabs the given user has to have to access this command
|
||||||
handler func(server *Server, client *Client, command string, params []string, rb *ResponseBuffer)
|
handler func(server *Server, client *Client, command string, params []string, rb *ResponseBuffer)
|
||||||
help string
|
help string
|
||||||
|
helpStrings []string
|
||||||
helpShort string
|
helpShort string
|
||||||
authRequired bool
|
authRequired bool
|
||||||
|
hidden bool
|
||||||
enabled func(*Config) bool // is this command enabled in the server config?
|
enabled func(*Config) bool // is this command enabled in the server config?
|
||||||
minParams int
|
minParams int
|
||||||
maxParams int // split into at most n params, with last param containing remaining unsplit text
|
maxParams int // split into at most n params, with last param containing remaining unsplit text
|
||||||
@ -139,7 +142,7 @@ func servicePrivmsgHandler(service *ircService, server *Server, client *Client,
|
|||||||
func serviceRunCommand(service *ircService, server *Server, client *Client, cmd *serviceCommand, commandName string, params []string, rb *ResponseBuffer) {
|
func serviceRunCommand(service *ircService, server *Server, client *Client, cmd *serviceCommand, commandName string, params []string, rb *ResponseBuffer) {
|
||||||
nick := rb.target.Nick()
|
nick := rb.target.Nick()
|
||||||
sendNotice := func(notice string) {
|
sendNotice := func(notice string) {
|
||||||
rb.Add(nil, service.Name, "NOTICE", nick, notice)
|
rb.Add(nil, service.prefix, "NOTICE", nick, notice)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd == nil {
|
if cmd == nil {
|
||||||
@ -180,7 +183,7 @@ func serviceHelpHandler(service *ircService, server *Server, client *Client, par
|
|||||||
nick := rb.target.Nick()
|
nick := rb.target.Nick()
|
||||||
config := server.Config()
|
config := server.Config()
|
||||||
sendNotice := func(notice string) {
|
sendNotice := func(notice string) {
|
||||||
rb.Add(nil, service.Name, "NOTICE", nick, notice)
|
rb.Add(nil, service.prefix, "NOTICE", nick, notice)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendNotice(ircfmt.Unescape(fmt.Sprintf("*** $b%s HELP$b ***", service.Name)))
|
sendNotice(ircfmt.Unescape(fmt.Sprintf("*** $b%s HELP$b ***", service.Name)))
|
||||||
@ -194,7 +197,7 @@ func serviceHelpHandler(service *ircService, server *Server, client *Client, par
|
|||||||
if 0 < len(commandInfo.capabs) && !client.HasRoleCapabs(commandInfo.capabs...) {
|
if 0 < len(commandInfo.capabs) && !client.HasRoleCapabs(commandInfo.capabs...) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if commandInfo.aliasOf != "" {
|
if commandInfo.aliasOf != "" || commandInfo.hidden {
|
||||||
continue // don't show help lines for aliases
|
continue // don't show help lines for aliases
|
||||||
}
|
}
|
||||||
if commandInfo.enabled != nil && !commandInfo.enabled(config) {
|
if commandInfo.enabled != nil && !commandInfo.enabled(config) {
|
||||||
@ -226,11 +229,21 @@ func serviceHelpHandler(service *ircService, server *Server, client *Client, par
|
|||||||
if commandInfo == nil {
|
if commandInfo == nil {
|
||||||
sendNotice(client.t(fmt.Sprintf("Unknown command. To see available commands, run /%s HELP", service.ShortName)))
|
sendNotice(client.t(fmt.Sprintf("Unknown command. To see available commands, run /%s HELP", service.ShortName)))
|
||||||
} else {
|
} else {
|
||||||
for _, line := range strings.Split(ircfmt.Unescape(client.t(commandInfo.help)), "\n") {
|
helpStrings := commandInfo.helpStrings
|
||||||
|
if helpStrings == nil {
|
||||||
|
hsArray := [1]string{commandInfo.help}
|
||||||
|
helpStrings = hsArray[:]
|
||||||
|
}
|
||||||
|
for i, helpString := range helpStrings {
|
||||||
|
if 0 < i {
|
||||||
|
sendNotice("")
|
||||||
|
}
|
||||||
|
for _, line := range strings.Split(ircfmt.Unescape(client.t(helpString)), "\n") {
|
||||||
sendNotice(line)
|
sendNotice(line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sendNotice(ircfmt.Unescape(fmt.Sprintf(client.t("*** $bEnd of %s HELP$b ***"), service.Name)))
|
sendNotice(ircfmt.Unescape(fmt.Sprintf(client.t("*** $bEnd of %s HELP$b ***"), service.Name)))
|
||||||
}
|
}
|
||||||
@ -241,6 +254,8 @@ func initializeServices() {
|
|||||||
oragonoServicesByCommandAlias = make(map[string]*ircService)
|
oragonoServicesByCommandAlias = make(map[string]*ircService)
|
||||||
|
|
||||||
for serviceName, service := range OragonoServices {
|
for serviceName, service := range OragonoServices {
|
||||||
|
service.prefix = fmt.Sprintf("%s!%s@localhost", service.Name, service.Name)
|
||||||
|
|
||||||
// make `/MSG ServiceName HELP` work correctly
|
// make `/MSG ServiceName HELP` work correctly
|
||||||
service.Commands["help"] = &servHelpCmd
|
service.Commands["help"] = &servHelpCmd
|
||||||
|
|
||||||
@ -257,9 +272,11 @@ func initializeServices() {
|
|||||||
|
|
||||||
// force devs to write a help entry for every command
|
// force devs to write a help entry for every command
|
||||||
for commandName, commandInfo := range service.Commands {
|
for commandName, commandInfo := range service.Commands {
|
||||||
if commandInfo.aliasOf == "" && (commandInfo.help == "" || commandInfo.helpShort == "") {
|
if commandInfo.aliasOf == "" && !commandInfo.hidden {
|
||||||
|
if (commandInfo.help == "" && commandInfo.helpStrings == nil) || commandInfo.helpShort == "" {
|
||||||
log.Fatal(fmt.Sprintf("help entry missing for %s command %s", serviceName, commandName))
|
log.Fatal(fmt.Sprintf("help entry missing for %s command %s", serviceName, commandName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,15 @@
|
|||||||
|
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidParams = errors.New("Invalid parameters")
|
||||||
|
)
|
||||||
|
|
||||||
// ArgsToStrings takes the arguments and splits them into a series of strings,
|
// ArgsToStrings takes the arguments and splits them into a series of strings,
|
||||||
// each argument separated by delim and each string bounded by maxLength.
|
// each argument separated by delim and each string bounded by maxLength.
|
||||||
func ArgsToStrings(maxLength int, arguments []string, delim string) []string {
|
func ArgsToStrings(maxLength int, arguments []string, delim string) []string {
|
||||||
@ -33,3 +42,15 @@ func ArgsToStrings(maxLength int, arguments []string, delim string) []string {
|
|||||||
|
|
||||||
return messages
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StringToBool(str string) (result bool, err error) {
|
||||||
|
switch strings.ToLower(str) {
|
||||||
|
case "on", "true", "t", "yes", "y":
|
||||||
|
result = true
|
||||||
|
case "off", "false", "f", "no", "n":
|
||||||
|
result = false
|
||||||
|
default:
|
||||||
|
err = ErrInvalidParams
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
23
irc/utils/args_test.go
Normal file
23
irc/utils/args_test.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) 2019 Shivaram Lingamneni <slingamn@cs.stanford.edu>
|
||||||
|
// released under the MIT license
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestStringToBool(t *testing.T) {
|
||||||
|
val, err := StringToBool("on")
|
||||||
|
assertEqual(val, true, t)
|
||||||
|
assertEqual(err, nil, t)
|
||||||
|
|
||||||
|
val, err = StringToBool("n")
|
||||||
|
assertEqual(val, false, t)
|
||||||
|
assertEqual(err, nil, t)
|
||||||
|
|
||||||
|
val, err = StringToBool("OFF")
|
||||||
|
assertEqual(val, false, t)
|
||||||
|
assertEqual(err, nil, t)
|
||||||
|
|
||||||
|
val, err = StringToBool("default")
|
||||||
|
assertEqual(err, ErrInvalidParams, t)
|
||||||
|
}
|
@ -134,7 +134,7 @@ server:
|
|||||||
# defaults to true when unset for that reason.
|
# defaults to true when unset for that reason.
|
||||||
force-trailing: true
|
force-trailing: true
|
||||||
|
|
||||||
# some clients (ZNC 1.6.x and lower, Pidgin 2.12 and lower, Adium) do not
|
# some clients (ZNC 1.6.x and lower, Pidgin 2.12 and lower) do not
|
||||||
# respond correctly to SASL messages with the server name as a prefix:
|
# respond correctly to SASL messages with the server name as a prefix:
|
||||||
# https://github.com/znc/znc/issues/1212
|
# https://github.com/znc/znc/issues/1212
|
||||||
# this works around that bug, allowing them to use SASL.
|
# this works around that bug, allowing them to use SASL.
|
||||||
|
Loading…
Reference in New Issue
Block a user