mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-25 21:39:25 +01:00
commit
c2c5fe7cf8
@ -475,23 +475,6 @@ accounts:
|
|||||||
# (make sure any changes you make here are RFC-compliant)
|
# (make sure any changes you make here are RFC-compliant)
|
||||||
valid-regexp: '^[0-9A-Za-z.\-_/]+$'
|
valid-regexp: '^[0-9A-Za-z.\-_/]+$'
|
||||||
|
|
||||||
# options controlling users requesting vhosts:
|
|
||||||
user-requests:
|
|
||||||
# can users request vhosts at all? if this is false, operators with the
|
|
||||||
# 'vhosts' capability can still assign vhosts manually
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# if uncommented, all new vhost requests will be dumped into the given
|
|
||||||
# channel, so opers can review them as they are sent in. ensure that you
|
|
||||||
# have registered and restricted the channel appropriately before you
|
|
||||||
# uncomment this.
|
|
||||||
#channel: "#vhosts"
|
|
||||||
|
|
||||||
# after a user's vhost has been approved or rejected, they need to wait
|
|
||||||
# this long (starting from the time of their original request)
|
|
||||||
# before they can request a new one.
|
|
||||||
cooldown: 168h
|
|
||||||
|
|
||||||
# modes that are set by default when a user connects
|
# modes that are set by default when a user connects
|
||||||
# if unset, no user modes will be set by default
|
# if unset, no user modes will be set by default
|
||||||
# +i is invisible (a user's channels are hidden from whois replies)
|
# +i is invisible (a user's channels are hidden from whois replies)
|
||||||
|
17
default.yaml
17
default.yaml
@ -503,23 +503,6 @@ accounts:
|
|||||||
# (make sure any changes you make here are RFC-compliant)
|
# (make sure any changes you make here are RFC-compliant)
|
||||||
valid-regexp: '^[0-9A-Za-z.\-_/]+$'
|
valid-regexp: '^[0-9A-Za-z.\-_/]+$'
|
||||||
|
|
||||||
# options controlling users requesting vhosts:
|
|
||||||
user-requests:
|
|
||||||
# can users request vhosts at all? if this is false, operators with the
|
|
||||||
# 'vhosts' capability can still assign vhosts manually
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
# if uncommented, all new vhost requests will be dumped into the given
|
|
||||||
# channel, so opers can review them as they are sent in. ensure that you
|
|
||||||
# have registered and restricted the channel appropriately before you
|
|
||||||
# uncomment this.
|
|
||||||
#channel: "#vhosts"
|
|
||||||
|
|
||||||
# after a user's vhost has been approved or rejected, they need to wait
|
|
||||||
# this long (starting from the time of their original request)
|
|
||||||
# before they can request a new one.
|
|
||||||
cooldown: 168h
|
|
||||||
|
|
||||||
# modes that are set by default when a user connects
|
# modes that are set by default when a user connects
|
||||||
# if unset, no user modes will be set by default
|
# if unset, no user modes will be set by default
|
||||||
# +i is invisible (a user's channels are hidden from whois replies)
|
# +i is invisible (a user's channels are hidden from whois replies)
|
||||||
|
204
irc/accounts.go
204
irc/accounts.go
@ -12,7 +12,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
@ -44,23 +43,14 @@ const (
|
|||||||
keyAccountModes = "account.modes %s" // user modes for the always-on client as a string
|
keyAccountModes = "account.modes %s" // user modes for the always-on client as a string
|
||||||
keyAccountRealname = "account.realname %s" // client realname stored as string
|
keyAccountRealname = "account.realname %s" // client realname stored as string
|
||||||
|
|
||||||
keyVHostQueueAcctToId = "vhostQueue %s"
|
|
||||||
vhostRequestIdx = "vhostQueue"
|
|
||||||
|
|
||||||
maxCertfpsPerAccount = 5
|
maxCertfpsPerAccount = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
// everything about accounts is persistent; therefore, the database is the authoritative
|
// everything about accounts is persistent; therefore, the database is the authoritative
|
||||||
// source of truth for all account information. anything on the heap is just a cache
|
// source of truth for all account information. anything on the heap is just a cache
|
||||||
type AccountManager struct {
|
type AccountManager struct {
|
||||||
// XXX these are up here so they can be aligned to a 64-bit boundary, please forgive me
|
|
||||||
// autoincrementing ID for vhost requests:
|
|
||||||
vhostRequestID uint64
|
|
||||||
vhostRequestPendingCount uint64
|
|
||||||
|
|
||||||
sync.RWMutex // tier 2
|
sync.RWMutex // tier 2
|
||||||
serialCacheUpdateMutex sync.Mutex // tier 3
|
serialCacheUpdateMutex sync.Mutex // tier 3
|
||||||
vHostUpdateMutex sync.Mutex // tier 3
|
|
||||||
|
|
||||||
server *Server
|
server *Server
|
||||||
// track clients logged in to accounts
|
// track clients logged in to accounts
|
||||||
@ -80,7 +70,6 @@ func (am *AccountManager) Initialize(server *Server) {
|
|||||||
|
|
||||||
config := server.Config()
|
config := server.Config()
|
||||||
am.buildNickToAccountIndex(config)
|
am.buildNickToAccountIndex(config)
|
||||||
am.initVHostRequestQueue(config)
|
|
||||||
am.createAlwaysOnClients(config)
|
am.createAlwaysOnClients(config)
|
||||||
am.resetRegisterThrottle(config)
|
am.resetRegisterThrottle(config)
|
||||||
}
|
}
|
||||||
@ -225,44 +214,6 @@ func (am *AccountManager) buildNickToAccountIndex(config *Config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) initVHostRequestQueue(config *Config) {
|
|
||||||
if !config.Accounts.VHosts.Enabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
am.vHostUpdateMutex.Lock()
|
|
||||||
defer am.vHostUpdateMutex.Unlock()
|
|
||||||
|
|
||||||
// the db maps the account name to the autoincrementing integer ID of its request
|
|
||||||
// create an numerically ordered index on ID, so we can list the oldest requests
|
|
||||||
// finally, collect the integer id of the newest request and the total request count
|
|
||||||
var total uint64
|
|
||||||
var lastIDStr string
|
|
||||||
err := am.server.store.Update(func(tx *buntdb.Tx) error {
|
|
||||||
err := tx.CreateIndex(vhostRequestIdx, fmt.Sprintf(keyVHostQueueAcctToId, "*"), buntdb.IndexInt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return tx.Descend(vhostRequestIdx, func(key, value string) bool {
|
|
||||||
if lastIDStr == "" {
|
|
||||||
lastIDStr = value
|
|
||||||
}
|
|
||||||
total++
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
am.server.logger.Error("internal", "could not create vhost queue index", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
lastID, _ := strconv.ParseUint(lastIDStr, 10, 64)
|
|
||||||
am.server.logger.Debug("services", fmt.Sprintf("vhost queue length is %d, autoincrementing id is %d", total, lastID))
|
|
||||||
|
|
||||||
atomic.StoreUint64(&am.vhostRequestID, lastID)
|
|
||||||
atomic.StoreUint64(&am.vhostRequestPendingCount, total)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *AccountManager) NickToAccount(nick string) string {
|
func (am *AccountManager) NickToAccount(nick string) string {
|
||||||
cfnick, err := CasefoldName(nick)
|
cfnick, err := CasefoldName(nick)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1394,7 +1345,6 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
|
|||||||
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
||||||
settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
||||||
vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
|
vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
|
||||||
vhostQueueKey := fmt.Sprintf(keyVHostQueueAcctToId, casefoldedAccount)
|
|
||||||
channelsKey := fmt.Sprintf(keyAccountChannels, casefoldedAccount)
|
channelsKey := fmt.Sprintf(keyAccountChannels, casefoldedAccount)
|
||||||
joinedChannelsKey := fmt.Sprintf(keyAccountJoinedChannels, casefoldedAccount)
|
joinedChannelsKey := fmt.Sprintf(keyAccountJoinedChannels, casefoldedAccount)
|
||||||
lastSeenKey := fmt.Sprintf(keyAccountLastSeen, casefoldedAccount)
|
lastSeenKey := fmt.Sprintf(keyAccountLastSeen, casefoldedAccount)
|
||||||
@ -1461,8 +1411,6 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
|
|||||||
tx.Delete(modesKey)
|
tx.Delete(modesKey)
|
||||||
tx.Delete(realnameKey)
|
tx.Delete(realnameKey)
|
||||||
|
|
||||||
_, err := tx.Delete(vhostQueueKey)
|
|
||||||
am.decrementVHostQueueCount(casefoldedAccount, err)
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1635,42 +1583,8 @@ func (am *AccountManager) ModifyAccountSettings(account string, munger settingsM
|
|||||||
|
|
||||||
// represents someone's status in hostserv
|
// represents someone's status in hostserv
|
||||||
type VHostInfo struct {
|
type VHostInfo struct {
|
||||||
ApprovedVHost string
|
ApprovedVHost string
|
||||||
Enabled bool
|
Enabled bool
|
||||||
RequestedVHost string
|
|
||||||
RejectedVHost string
|
|
||||||
RejectionReason string
|
|
||||||
LastRequestTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// pair type, <VHostInfo, accountName>
|
|
||||||
type PendingVHostRequest struct {
|
|
||||||
VHostInfo
|
|
||||||
Account string
|
|
||||||
}
|
|
||||||
|
|
||||||
type vhostThrottleExceeded struct {
|
|
||||||
timeRemaining time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vhe *vhostThrottleExceeded) Error() string {
|
|
||||||
return fmt.Sprintf("Wait at least %v and try again", vhe.timeRemaining)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vh *VHostInfo) checkThrottle(cooldown time.Duration) (err error) {
|
|
||||||
if cooldown == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now().UTC()
|
|
||||||
elapsed := now.Sub(vh.LastRequestTime)
|
|
||||||
if elapsed > cooldown {
|
|
||||||
// success
|
|
||||||
vh.LastRequestTime = now
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return &vhostThrottleExceeded{timeRemaining: cooldown - elapsed}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// callback type implementing the actual business logic of vhost operations
|
// callback type implementing the actual business logic of vhost operations
|
||||||
@ -1687,52 +1601,6 @@ func (am *AccountManager) VHostSet(account string, vhost string) (result VHostIn
|
|||||||
return am.performVHostChange(account, munger)
|
return am.performVHostChange(account, munger)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) VHostRequest(account string, vhost string, cooldown time.Duration) (result VHostInfo, err error) {
|
|
||||||
munger := func(input VHostInfo) (output VHostInfo, err error) {
|
|
||||||
output = input
|
|
||||||
// you can update your existing request, but if you were approved or rejected,
|
|
||||||
// you can't spam a new request
|
|
||||||
if output.RequestedVHost == "" {
|
|
||||||
err = output.checkThrottle(cooldown)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
output.RequestedVHost = vhost
|
|
||||||
output.RejectedVHost = ""
|
|
||||||
output.RejectionReason = ""
|
|
||||||
output.LastRequestTime = time.Now().UTC()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return am.performVHostChange(account, munger)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *AccountManager) VHostApprove(account string) (result VHostInfo, err error) {
|
|
||||||
munger := func(input VHostInfo) (output VHostInfo, err error) {
|
|
||||||
output = input
|
|
||||||
output.Enabled = true
|
|
||||||
output.ApprovedVHost = input.RequestedVHost
|
|
||||||
output.RequestedVHost = ""
|
|
||||||
output.RejectionReason = ""
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return am.performVHostChange(account, munger)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *AccountManager) VHostReject(account string, reason string) (result VHostInfo, err error) {
|
|
||||||
munger := func(input VHostInfo) (output VHostInfo, err error) {
|
|
||||||
output = input
|
|
||||||
output.RejectedVHost = output.RequestedVHost
|
|
||||||
output.RequestedVHost = ""
|
|
||||||
output.RejectionReason = reason
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return am.performVHostChange(account, munger)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *AccountManager) VHostSetEnabled(client *Client, enabled bool) (result VHostInfo, err error) {
|
func (am *AccountManager) VHostSetEnabled(client *Client, enabled bool) (result VHostInfo, err error) {
|
||||||
munger := func(input VHostInfo) (output VHostInfo, err error) {
|
munger := func(input VHostInfo) (output VHostInfo, err error) {
|
||||||
if input.ApprovedVHost == "" {
|
if input.ApprovedVHost == "" {
|
||||||
@ -1759,9 +1627,6 @@ func (am *AccountManager) performVHostChange(account string, munger vhostMunger)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
am.vHostUpdateMutex.Lock()
|
|
||||||
defer am.vHostUpdateMutex.Unlock()
|
|
||||||
|
|
||||||
clientAccount, err := am.LoadAccount(account)
|
clientAccount, err := am.LoadAccount(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errAccountDoesNotExist
|
err = errAccountDoesNotExist
|
||||||
@ -1784,25 +1649,9 @@ func (am *AccountManager) performVHostChange(account string, munger vhostMunger)
|
|||||||
vhstr := string(vhtext)
|
vhstr := string(vhtext)
|
||||||
|
|
||||||
key := fmt.Sprintf(keyAccountVHost, account)
|
key := fmt.Sprintf(keyAccountVHost, account)
|
||||||
queueKey := fmt.Sprintf(keyVHostQueueAcctToId, account)
|
|
||||||
err = am.server.store.Update(func(tx *buntdb.Tx) error {
|
err = am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||||
if _, _, err := tx.Set(key, vhstr, nil); err != nil {
|
_, _, err := tx.Set(key, vhstr, nil)
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
// update request queue
|
|
||||||
if clientAccount.VHost.RequestedVHost == "" && result.RequestedVHost != "" {
|
|
||||||
id := atomic.AddUint64(&am.vhostRequestID, 1)
|
|
||||||
if _, _, err = tx.Set(queueKey, strconv.FormatUint(id, 10), nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
atomic.AddUint64(&am.vhostRequestPendingCount, 1)
|
|
||||||
} else if clientAccount.VHost.RequestedVHost != "" && result.RequestedVHost == "" {
|
|
||||||
_, err = tx.Delete(queueKey)
|
|
||||||
am.decrementVHostQueueCount(account, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1814,51 +1663,6 @@ func (am *AccountManager) performVHostChange(account string, munger vhostMunger)
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX annoying helper method for keeping the queue count in sync with the DB
|
|
||||||
// `err` is the buntdb error returned from deleting the queue key
|
|
||||||
func (am *AccountManager) decrementVHostQueueCount(account string, err error) {
|
|
||||||
if err == nil {
|
|
||||||
// successfully deleted a queue entry, do a 2's complement decrement:
|
|
||||||
atomic.AddUint64(&am.vhostRequestPendingCount, ^uint64(0))
|
|
||||||
} else if err != buntdb.ErrNotFound {
|
|
||||||
am.server.logger.Error("internal", "buntdb dequeue error", account, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *AccountManager) VHostListRequests(limit int) (requests []PendingVHostRequest, total int) {
|
|
||||||
am.vHostUpdateMutex.Lock()
|
|
||||||
defer am.vHostUpdateMutex.Unlock()
|
|
||||||
|
|
||||||
total = int(atomic.LoadUint64(&am.vhostRequestPendingCount))
|
|
||||||
|
|
||||||
prefix := fmt.Sprintf(keyVHostQueueAcctToId, "")
|
|
||||||
accounts := make([]string, 0, limit)
|
|
||||||
err := am.server.store.View(func(tx *buntdb.Tx) error {
|
|
||||||
return tx.Ascend(vhostRequestIdx, func(key, value string) bool {
|
|
||||||
accounts = append(accounts, strings.TrimPrefix(key, prefix))
|
|
||||||
return len(accounts) < limit
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
am.server.logger.Error("internal", "couldn't traverse vhost queue", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, account := range accounts {
|
|
||||||
accountInfo, err := am.LoadAccount(account)
|
|
||||||
if err == nil {
|
|
||||||
requests = append(requests, PendingVHostRequest{
|
|
||||||
Account: account,
|
|
||||||
VHostInfo: accountInfo.VHost,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
am.server.logger.Error("internal", "corrupt account", account, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *AccountManager) applyVHostInfo(client *Client, info VHostInfo) {
|
func (am *AccountManager) applyVHostInfo(client *Client, info VHostInfo) {
|
||||||
// if hostserv is disabled in config, then don't grant vhosts
|
// if hostserv is disabled in config, then don't grant vhosts
|
||||||
// that were previously approved while it was enabled
|
// that were previously approved while it was enabled
|
||||||
|
@ -316,12 +316,7 @@ type VHostConfig struct {
|
|||||||
Enabled bool
|
Enabled bool
|
||||||
MaxLength int `yaml:"max-length"`
|
MaxLength int `yaml:"max-length"`
|
||||||
ValidRegexpRaw string `yaml:"valid-regexp"`
|
ValidRegexpRaw string `yaml:"valid-regexp"`
|
||||||
ValidRegexp *regexp.Regexp
|
validRegexp *regexp.Regexp
|
||||||
UserRequests struct {
|
|
||||||
Enabled bool
|
|
||||||
Channel string
|
|
||||||
Cooldown custime.Duration
|
|
||||||
} `yaml:"user-requests"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type NickEnforcementMethod int
|
type NickEnforcementMethod int
|
||||||
@ -1109,13 +1104,13 @@ func LoadConfig(filename string) (config *Config, err error) {
|
|||||||
if rawRegexp != "" {
|
if rawRegexp != "" {
|
||||||
regexp, err := regexp.Compile(rawRegexp)
|
regexp, err := regexp.Compile(rawRegexp)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
config.Accounts.VHosts.ValidRegexp = regexp
|
config.Accounts.VHosts.validRegexp = regexp
|
||||||
} else {
|
} else {
|
||||||
log.Printf("invalid vhost regexp: %s\n", err.Error())
|
log.Printf("invalid vhost regexp: %s\n", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if config.Accounts.VHosts.ValidRegexp == nil {
|
if config.Accounts.VHosts.validRegexp == nil {
|
||||||
config.Accounts.VHosts.ValidRegexp = defaultValidVhostRegex
|
config.Accounts.VHosts.validRegexp = defaultValidVhostRegex
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Server.capValues[caps.SASL] = "PLAIN,EXTERNAL"
|
config.Server.capValues[caps.SASL] = "PLAIN,EXTERNAL"
|
||||||
|
@ -24,7 +24,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 = "16"
|
latestDbSchema = "17"
|
||||||
|
|
||||||
keyCloakSecret = "crypto.cloak_secret"
|
keyCloakSecret = "crypto.cloak_secret"
|
||||||
)
|
)
|
||||||
@ -835,6 +835,24 @@ func schemaChangeV15ToV16(config *Config, tx *buntdb.Tx) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #1346: remove vhost request queue
|
||||||
|
func schemaChangeV16ToV17(config *Config, tx *buntdb.Tx) error {
|
||||||
|
prefix := "vhostQueue "
|
||||||
|
var keys []string
|
||||||
|
tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
|
||||||
|
if !strings.HasPrefix(key, prefix) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
keys = append(keys, key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
tx.Delete(key)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
allChanges := []SchemaChange{
|
allChanges := []SchemaChange{
|
||||||
{
|
{
|
||||||
@ -912,6 +930,11 @@ func init() {
|
|||||||
TargetVersion: "16",
|
TargetVersion: "16",
|
||||||
Changer: schemaChangeV15ToV16,
|
Changer: schemaChangeV15ToV16,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
InitialVersion: "16",
|
||||||
|
TargetVersion: "17",
|
||||||
|
Changer: schemaChangeV16ToV17,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// build the index
|
// build the index
|
||||||
|
145
irc/hostserv.go
145
irc/hostserv.go
@ -7,11 +7,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/goshuirc/irc-go/ircfmt"
|
"github.com/goshuirc/irc-go/ircfmt"
|
||||||
|
|
||||||
"github.com/oragono/oragono/irc/sno"
|
|
||||||
"github.com/oragono/oragono/irc/utils"
|
"github.com/oragono/oragono/irc/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,10 +30,6 @@ func hostservEnabled(config *Config) bool {
|
|||||||
return config.Accounts.VHosts.Enabled
|
return config.Accounts.VHosts.Enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
func hostservRequestsEnabled(config *Config) bool {
|
|
||||||
return config.Accounts.VHosts.Enabled && config.Accounts.VHosts.UserRequests.Enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
hostservCommands = map[string]*serviceCommand{
|
hostservCommands = map[string]*serviceCommand{
|
||||||
"on": {
|
"on": {
|
||||||
@ -56,17 +50,6 @@ OFF disables your vhost, if you have one approved.`,
|
|||||||
authRequired: true,
|
authRequired: true,
|
||||||
enabled: hostservEnabled,
|
enabled: hostservEnabled,
|
||||||
},
|
},
|
||||||
"request": {
|
|
||||||
handler: hsRequestHandler,
|
|
||||||
help: `Syntax: $bREQUEST <vhost>$b
|
|
||||||
|
|
||||||
REQUEST requests that a new vhost by assigned to your account. The request must
|
|
||||||
then be approved by a server operator.`,
|
|
||||||
helpShort: `$bREQUEST$b requests a new vhost, pending operator approval.`,
|
|
||||||
authRequired: true,
|
|
||||||
enabled: hostservRequestsEnabled,
|
|
||||||
minParams: 1,
|
|
||||||
},
|
|
||||||
"status": {
|
"status": {
|
||||||
handler: hsStatusHandler,
|
handler: hsStatusHandler,
|
||||||
help: `Syntax: $bSTATUS [user]$b
|
help: `Syntax: $bSTATUS [user]$b
|
||||||
@ -96,39 +79,6 @@ DEL deletes a user's vhost.`,
|
|||||||
enabled: hostservEnabled,
|
enabled: hostservEnabled,
|
||||||
minParams: 1,
|
minParams: 1,
|
||||||
},
|
},
|
||||||
"waiting": {
|
|
||||||
handler: hsWaitingHandler,
|
|
||||||
help: `Syntax: $bWAITING$b
|
|
||||||
|
|
||||||
WAITING shows a list of pending vhost requests, which can then be approved
|
|
||||||
or rejected.`,
|
|
||||||
helpShort: `$bWAITING$b shows a list of pending vhost requests.`,
|
|
||||||
capabs: []string{"vhosts"},
|
|
||||||
enabled: hostservEnabled,
|
|
||||||
},
|
|
||||||
"approve": {
|
|
||||||
handler: hsApproveHandler,
|
|
||||||
help: `Syntax: $bAPPROVE <user>$b
|
|
||||||
|
|
||||||
APPROVE approves a user's vhost request.`,
|
|
||||||
helpShort: `$bAPPROVE$b approves a user's vhost request.`,
|
|
||||||
capabs: []string{"vhosts"},
|
|
||||||
enabled: hostservEnabled,
|
|
||||||
minParams: 1,
|
|
||||||
},
|
|
||||||
"reject": {
|
|
||||||
handler: hsRejectHandler,
|
|
||||||
help: `Syntax: $bREJECT <user> [<reason>]$b
|
|
||||||
|
|
||||||
REJECT rejects a user's vhost request, optionally giving them a reason
|
|
||||||
for the rejection.`,
|
|
||||||
helpShort: `$bREJECT$b rejects a user's vhost request.`,
|
|
||||||
capabs: []string{"vhosts"},
|
|
||||||
enabled: hostservEnabled,
|
|
||||||
minParams: 1,
|
|
||||||
maxParams: 2,
|
|
||||||
unsplitFinalParam: true,
|
|
||||||
},
|
|
||||||
"setcloaksecret": {
|
"setcloaksecret": {
|
||||||
handler: hsSetCloakSecretHandler,
|
handler: hsSetCloakSecretHandler,
|
||||||
help: `Syntax: $bSETCLOAKSECRET$b <secret> [code]
|
help: `Syntax: $bSETCLOAKSECRET$b <secret> [code]
|
||||||
@ -150,19 +100,6 @@ func hsNotice(rb *ResponseBuffer, text string) {
|
|||||||
rb.Add(nil, hsNickMask, "NOTICE", rb.target.Nick(), text)
|
rb.Add(nil, hsNickMask, "NOTICE", rb.target.Nick(), text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// hsNotifyChannel notifies the designated channel of new vhost activity
|
|
||||||
func hsNotifyChannel(server *Server, message string) {
|
|
||||||
chname := server.Config().Accounts.VHosts.UserRequests.Channel
|
|
||||||
channel := server.channels.Get(chname)
|
|
||||||
if channel == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
chname = channel.Name()
|
|
||||||
for _, client := range channel.Members() {
|
|
||||||
client.Send(nil, hsNickMask, "PRIVMSG", chname, message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func hsOnOffHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
func hsOnOffHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
enable := false
|
enable := false
|
||||||
if command == "on" {
|
if command == "on" {
|
||||||
@ -181,29 +118,6 @@ func hsOnOffHandler(server *Server, client *Client, command string, params []str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hsRequestHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
||||||
vhost := params[0]
|
|
||||||
if validateVhost(server, vhost, false) != nil {
|
|
||||||
hsNotice(rb, client.t("Invalid vhost"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
accountName := client.Account()
|
|
||||||
_, err := server.accounts.VHostRequest(accountName, vhost, time.Duration(server.Config().Accounts.VHosts.UserRequests.Cooldown))
|
|
||||||
if err != nil {
|
|
||||||
if throttled, ok := err.(*vhostThrottleExceeded); ok {
|
|
||||||
hsNotice(rb, fmt.Sprintf(client.t("You must wait an additional %v before making another request"), throttled.timeRemaining))
|
|
||||||
} else {
|
|
||||||
hsNotice(rb, client.t("An error occurred"))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hsNotice(rb, client.t("Your vhost request will be reviewed by an administrator"))
|
|
||||||
chanMsg := fmt.Sprintf("Account %s requests vhost %s", accountName, vhost)
|
|
||||||
hsNotifyChannel(server, chanMsg)
|
|
||||||
server.snomasks.Send(sno.LocalVhosts, chanMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func hsStatusHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
func hsStatusHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
var accountName string
|
var accountName string
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
@ -237,13 +151,6 @@ func hsStatusHandler(server *Server, client *Client, command string, params []st
|
|||||||
} else {
|
} else {
|
||||||
hsNotice(rb, fmt.Sprintf(client.t("Account %s has no vhost"), accountName))
|
hsNotice(rb, fmt.Sprintf(client.t("Account %s has no vhost"), accountName))
|
||||||
}
|
}
|
||||||
if account.VHost.RequestedVHost != "" {
|
|
||||||
hsNotice(rb, fmt.Sprintf(client.t("A request is pending for vhost: %s"), account.VHost.RequestedVHost))
|
|
||||||
}
|
|
||||||
if account.VHost.RejectedVHost != "" {
|
|
||||||
hsNotice(rb, fmt.Sprintf(client.t("A request was previously made for vhost: %s"), account.VHost.RejectedVHost))
|
|
||||||
hsNotice(rb, fmt.Sprintf(client.t("It was rejected for reason: %s"), account.VHost.RejectionReason))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateVhost(server *Server, vhost string, oper bool) error {
|
func validateVhost(server *Server, vhost string, oper bool) error {
|
||||||
@ -251,7 +158,7 @@ func validateVhost(server *Server, vhost string, oper bool) error {
|
|||||||
if len(vhost) > config.Accounts.VHosts.MaxLength {
|
if len(vhost) > config.Accounts.VHosts.MaxLength {
|
||||||
return errVHostTooLong
|
return errVHostTooLong
|
||||||
}
|
}
|
||||||
if !config.Accounts.VHosts.ValidRegexp.MatchString(vhost) {
|
if !config.Accounts.VHosts.validRegexp.MatchString(vhost) {
|
||||||
return errVHostBadCharacters
|
return errVHostBadCharacters
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -280,56 +187,6 @@ func hsSetHandler(server *Server, client *Client, command string, params []strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hsWaitingHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
||||||
requests, total := server.accounts.VHostListRequests(10)
|
|
||||||
hsNotice(rb, fmt.Sprintf(client.t("There are %[1]d pending requests for vhosts (%[2]d displayed)"), total, len(requests)))
|
|
||||||
for i, request := range requests {
|
|
||||||
hsNotice(rb, fmt.Sprintf(client.t("%[1]d. User %[2]s requests vhost: %[3]s"), i+1, request.Account, request.RequestedVHost))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func hsApproveHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
||||||
user := params[0]
|
|
||||||
|
|
||||||
vhostInfo, err := server.accounts.VHostApprove(user)
|
|
||||||
if err != nil {
|
|
||||||
hsNotice(rb, client.t("An error occurred"))
|
|
||||||
} else {
|
|
||||||
hsNotice(rb, fmt.Sprintf(client.t("Successfully approved vhost request for %s"), user))
|
|
||||||
chanMsg := fmt.Sprintf("Oper %[1]s approved vhost %[2]s for account %[3]s", client.Nick(), vhostInfo.ApprovedVHost, user)
|
|
||||||
hsNotifyChannel(server, chanMsg)
|
|
||||||
server.snomasks.Send(sno.LocalVhosts, chanMsg)
|
|
||||||
for _, client := range server.accounts.AccountToClients(user) {
|
|
||||||
client.Send(nil, hsNickMask, "NOTICE", client.Nick(), client.t("Your vhost request was approved by an administrator"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func hsRejectHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
|
||||||
var reason string
|
|
||||||
user := params[0]
|
|
||||||
if len(params) > 1 {
|
|
||||||
reason = params[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
vhostInfo, err := server.accounts.VHostReject(user, reason)
|
|
||||||
if err != nil {
|
|
||||||
hsNotice(rb, client.t("An error occurred"))
|
|
||||||
} else {
|
|
||||||
hsNotice(rb, fmt.Sprintf(client.t("Successfully rejected vhost request for %s"), user))
|
|
||||||
chanMsg := fmt.Sprintf("Oper %s rejected vhost %s for account %s, with the reason: %v", client.Nick(), vhostInfo.RejectedVHost, user, reason)
|
|
||||||
hsNotifyChannel(server, chanMsg)
|
|
||||||
server.snomasks.Send(sno.LocalVhosts, chanMsg)
|
|
||||||
for _, client := range server.accounts.AccountToClients(user) {
|
|
||||||
if reason == "" {
|
|
||||||
client.Send(nil, hsNickMask, "NOTICE", client.Nick(), client.t("Your vhost request was rejected by an administrator"))
|
|
||||||
} else {
|
|
||||||
client.Send(nil, hsNickMask, "NOTICE", client.Nick(), fmt.Sprintf(client.t("Your vhost request was rejected by an administrator. The reason given was: %s"), reason))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func hsSetCloakSecretHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
func hsSetCloakSecretHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
secret := params[0]
|
secret := params[0]
|
||||||
expectedCode := utils.ConfirmationCode(secret, server.ctime)
|
expectedCode := utils.ConfirmationCode(secret, server.ctime)
|
||||||
|
@ -69,8 +69,8 @@ func doImportDBGeneric(config *Config, dbImport databaseImport, credsType Creden
|
|||||||
// produce a hardcoded version of the database schema
|
// produce a hardcoded version of the database schema
|
||||||
// XXX instead of referencing, e.g., keyAccountExists, we should write in the string literal
|
// XXX instead of referencing, e.g., keyAccountExists, we should write in the string literal
|
||||||
// (to ensure that no matter what code changes happen elsewhere, we're still producing a
|
// (to ensure that no matter what code changes happen elsewhere, we're still producing a
|
||||||
// version 14 db)
|
// db of the hardcoded version)
|
||||||
tx.Set(keySchemaVersion, "14", nil)
|
tx.Set(keySchemaVersion, "17", nil)
|
||||||
tx.Set(keyCloakSecret, utils.GenerateSecretKey(), nil)
|
tx.Set(keyCloakSecret, utils.GenerateSecretKey(), nil)
|
||||||
|
|
||||||
for username, userInfo := range dbImport.Users {
|
for username, userInfo := range dbImport.Users {
|
||||||
|
@ -570,9 +570,6 @@ func (server *Server) applyConfig(config *Config) (err error) {
|
|||||||
if !oldConfig.Accounts.NickReservation.Enabled {
|
if !oldConfig.Accounts.NickReservation.Enabled {
|
||||||
server.accounts.buildNickToAccountIndex(config)
|
server.accounts.buildNickToAccountIndex(config)
|
||||||
}
|
}
|
||||||
if !oldConfig.Accounts.VHosts.Enabled {
|
|
||||||
server.accounts.initVHostRequestQueue(config)
|
|
||||||
}
|
|
||||||
if !oldConfig.Channels.Registration.Enabled {
|
if !oldConfig.Channels.Registration.Enabled {
|
||||||
server.channels.loadRegisteredChannels(config)
|
server.channels.loadRegisteredChannels(config)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user