|
@@ -12,7 +12,6 @@ import (
|
12
|
12
|
"strconv"
|
13
|
13
|
"strings"
|
14
|
14
|
"sync"
|
15
|
|
- "sync/atomic"
|
16
|
15
|
"time"
|
17
|
16
|
"unicode"
|
18
|
17
|
|
|
@@ -44,23 +43,14 @@ const (
|
44
|
43
|
keyAccountModes = "account.modes %s" // user modes for the always-on client as a string
|
45
|
44
|
keyAccountRealname = "account.realname %s" // client realname stored as string
|
46
|
45
|
|
47
|
|
- keyVHostQueueAcctToId = "vhostQueue %s"
|
48
|
|
- vhostRequestIdx = "vhostQueue"
|
49
|
|
-
|
50
|
46
|
maxCertfpsPerAccount = 5
|
51
|
47
|
)
|
52
|
48
|
|
53
|
49
|
// everything about accounts is persistent; therefore, the database is the authoritative
|
54
|
50
|
// source of truth for all account information. anything on the heap is just a cache
|
55
|
51
|
type AccountManager struct {
|
56
|
|
- // XXX these are up here so they can be aligned to a 64-bit boundary, please forgive me
|
57
|
|
- // autoincrementing ID for vhost requests:
|
58
|
|
- vhostRequestID uint64
|
59
|
|
- vhostRequestPendingCount uint64
|
60
|
|
-
|
61
|
52
|
sync.RWMutex // tier 2
|
62
|
53
|
serialCacheUpdateMutex sync.Mutex // tier 3
|
63
|
|
- vHostUpdateMutex sync.Mutex // tier 3
|
64
|
54
|
|
65
|
55
|
server *Server
|
66
|
56
|
// track clients logged in to accounts
|
|
@@ -80,7 +70,6 @@ func (am *AccountManager) Initialize(server *Server) {
|
80
|
70
|
|
81
|
71
|
config := server.Config()
|
82
|
72
|
am.buildNickToAccountIndex(config)
|
83
|
|
- am.initVHostRequestQueue(config)
|
84
|
73
|
am.createAlwaysOnClients(config)
|
85
|
74
|
am.resetRegisterThrottle(config)
|
86
|
75
|
}
|
|
@@ -225,44 +214,6 @@ func (am *AccountManager) buildNickToAccountIndex(config *Config) {
|
225
|
214
|
}
|
226
|
215
|
}
|
227
|
216
|
|
228
|
|
-func (am *AccountManager) initVHostRequestQueue(config *Config) {
|
229
|
|
- if !config.Accounts.VHosts.Enabled {
|
230
|
|
- return
|
231
|
|
- }
|
232
|
|
-
|
233
|
|
- am.vHostUpdateMutex.Lock()
|
234
|
|
- defer am.vHostUpdateMutex.Unlock()
|
235
|
|
-
|
236
|
|
- // the db maps the account name to the autoincrementing integer ID of its request
|
237
|
|
- // create an numerically ordered index on ID, so we can list the oldest requests
|
238
|
|
- // finally, collect the integer id of the newest request and the total request count
|
239
|
|
- var total uint64
|
240
|
|
- var lastIDStr string
|
241
|
|
- err := am.server.store.Update(func(tx *buntdb.Tx) error {
|
242
|
|
- err := tx.CreateIndex(vhostRequestIdx, fmt.Sprintf(keyVHostQueueAcctToId, "*"), buntdb.IndexInt)
|
243
|
|
- if err != nil {
|
244
|
|
- return err
|
245
|
|
- }
|
246
|
|
- return tx.Descend(vhostRequestIdx, func(key, value string) bool {
|
247
|
|
- if lastIDStr == "" {
|
248
|
|
- lastIDStr = value
|
249
|
|
- }
|
250
|
|
- total++
|
251
|
|
- return true
|
252
|
|
- })
|
253
|
|
- })
|
254
|
|
-
|
255
|
|
- if err != nil {
|
256
|
|
- am.server.logger.Error("internal", "could not create vhost queue index", err.Error())
|
257
|
|
- }
|
258
|
|
-
|
259
|
|
- lastID, _ := strconv.ParseUint(lastIDStr, 10, 64)
|
260
|
|
- am.server.logger.Debug("services", fmt.Sprintf("vhost queue length is %d, autoincrementing id is %d", total, lastID))
|
261
|
|
-
|
262
|
|
- atomic.StoreUint64(&am.vhostRequestID, lastID)
|
263
|
|
- atomic.StoreUint64(&am.vhostRequestPendingCount, total)
|
264
|
|
-}
|
265
|
|
-
|
266
|
217
|
func (am *AccountManager) NickToAccount(nick string) string {
|
267
|
218
|
cfnick, err := CasefoldName(nick)
|
268
|
219
|
if err != nil {
|
|
@@ -1394,7 +1345,6 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
|
1394
|
1345
|
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
1395
|
1346
|
settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
1396
|
1347
|
vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
|
1397
|
|
- vhostQueueKey := fmt.Sprintf(keyVHostQueueAcctToId, casefoldedAccount)
|
1398
|
1348
|
channelsKey := fmt.Sprintf(keyAccountChannels, casefoldedAccount)
|
1399
|
1349
|
joinedChannelsKey := fmt.Sprintf(keyAccountJoinedChannels, casefoldedAccount)
|
1400
|
1350
|
lastSeenKey := fmt.Sprintf(keyAccountLastSeen, casefoldedAccount)
|
|
@@ -1461,8 +1411,6 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
|
1461
|
1411
|
tx.Delete(modesKey)
|
1462
|
1412
|
tx.Delete(realnameKey)
|
1463
|
1413
|
|
1464
|
|
- _, err := tx.Delete(vhostQueueKey)
|
1465
|
|
- am.decrementVHostQueueCount(casefoldedAccount, err)
|
1466
|
1414
|
return nil
|
1467
|
1415
|
})
|
1468
|
1416
|
|
|
@@ -1635,42 +1583,8 @@ func (am *AccountManager) ModifyAccountSettings(account string, munger settingsM
|
1635
|
1583
|
|
1636
|
1584
|
// represents someone's status in hostserv
|
1637
|
1585
|
type VHostInfo struct {
|
1638
|
|
- ApprovedVHost string
|
1639
|
|
- Enabled bool
|
1640
|
|
- RequestedVHost string
|
1641
|
|
- RejectedVHost string
|
1642
|
|
- RejectionReason string
|
1643
|
|
- LastRequestTime time.Time
|
1644
|
|
-}
|
1645
|
|
-
|
1646
|
|
-// pair type, <VHostInfo, accountName>
|
1647
|
|
-type PendingVHostRequest struct {
|
1648
|
|
- VHostInfo
|
1649
|
|
- Account string
|
1650
|
|
-}
|
1651
|
|
-
|
1652
|
|
-type vhostThrottleExceeded struct {
|
1653
|
|
- timeRemaining time.Duration
|
1654
|
|
-}
|
1655
|
|
-
|
1656
|
|
-func (vhe *vhostThrottleExceeded) Error() string {
|
1657
|
|
- return fmt.Sprintf("Wait at least %v and try again", vhe.timeRemaining)
|
1658
|
|
-}
|
1659
|
|
-
|
1660
|
|
-func (vh *VHostInfo) checkThrottle(cooldown time.Duration) (err error) {
|
1661
|
|
- if cooldown == 0 {
|
1662
|
|
- return nil
|
1663
|
|
- }
|
1664
|
|
-
|
1665
|
|
- now := time.Now().UTC()
|
1666
|
|
- elapsed := now.Sub(vh.LastRequestTime)
|
1667
|
|
- if elapsed > cooldown {
|
1668
|
|
- // success
|
1669
|
|
- vh.LastRequestTime = now
|
1670
|
|
- return nil
|
1671
|
|
- } else {
|
1672
|
|
- return &vhostThrottleExceeded{timeRemaining: cooldown - elapsed}
|
1673
|
|
- }
|
|
1586
|
+ ApprovedVHost string
|
|
1587
|
+ Enabled bool
|
1674
|
1588
|
}
|
1675
|
1589
|
|
1676
|
1590
|
// callback type implementing the actual business logic of vhost operations
|
|
@@ -1687,52 +1601,6 @@ func (am *AccountManager) VHostSet(account string, vhost string) (result VHostIn
|
1687
|
1601
|
return am.performVHostChange(account, munger)
|
1688
|
1602
|
}
|
1689
|
1603
|
|
1690
|
|
-func (am *AccountManager) VHostRequest(account string, vhost string, cooldown time.Duration) (result VHostInfo, err error) {
|
1691
|
|
- munger := func(input VHostInfo) (output VHostInfo, err error) {
|
1692
|
|
- output = input
|
1693
|
|
- // you can update your existing request, but if you were approved or rejected,
|
1694
|
|
- // you can't spam a new request
|
1695
|
|
- if output.RequestedVHost == "" {
|
1696
|
|
- err = output.checkThrottle(cooldown)
|
1697
|
|
- }
|
1698
|
|
- if err != nil {
|
1699
|
|
- return
|
1700
|
|
- }
|
1701
|
|
- output.RequestedVHost = vhost
|
1702
|
|
- output.RejectedVHost = ""
|
1703
|
|
- output.RejectionReason = ""
|
1704
|
|
- output.LastRequestTime = time.Now().UTC()
|
1705
|
|
- return
|
1706
|
|
- }
|
1707
|
|
-
|
1708
|
|
- return am.performVHostChange(account, munger)
|
1709
|
|
-}
|
1710
|
|
-
|
1711
|
|
-func (am *AccountManager) VHostApprove(account string) (result VHostInfo, err error) {
|
1712
|
|
- munger := func(input VHostInfo) (output VHostInfo, err error) {
|
1713
|
|
- output = input
|
1714
|
|
- output.Enabled = true
|
1715
|
|
- output.ApprovedVHost = input.RequestedVHost
|
1716
|
|
- output.RequestedVHost = ""
|
1717
|
|
- output.RejectionReason = ""
|
1718
|
|
- return
|
1719
|
|
- }
|
1720
|
|
-
|
1721
|
|
- return am.performVHostChange(account, munger)
|
1722
|
|
-}
|
1723
|
|
-
|
1724
|
|
-func (am *AccountManager) VHostReject(account string, reason string) (result VHostInfo, err error) {
|
1725
|
|
- munger := func(input VHostInfo) (output VHostInfo, err error) {
|
1726
|
|
- output = input
|
1727
|
|
- output.RejectedVHost = output.RequestedVHost
|
1728
|
|
- output.RequestedVHost = ""
|
1729
|
|
- output.RejectionReason = reason
|
1730
|
|
- return
|
1731
|
|
- }
|
1732
|
|
-
|
1733
|
|
- return am.performVHostChange(account, munger)
|
1734
|
|
-}
|
1735
|
|
-
|
1736
|
1604
|
func (am *AccountManager) VHostSetEnabled(client *Client, enabled bool) (result VHostInfo, err error) {
|
1737
|
1605
|
munger := func(input VHostInfo) (output VHostInfo, err error) {
|
1738
|
1606
|
if input.ApprovedVHost == "" {
|
|
@@ -1759,9 +1627,6 @@ func (am *AccountManager) performVHostChange(account string, munger vhostMunger)
|
1759
|
1627
|
return
|
1760
|
1628
|
}
|
1761
|
1629
|
|
1762
|
|
- am.vHostUpdateMutex.Lock()
|
1763
|
|
- defer am.vHostUpdateMutex.Unlock()
|
1764
|
|
-
|
1765
|
1630
|
clientAccount, err := am.LoadAccount(account)
|
1766
|
1631
|
if err != nil {
|
1767
|
1632
|
err = errAccountDoesNotExist
|
|
@@ -1784,25 +1649,9 @@ func (am *AccountManager) performVHostChange(account string, munger vhostMunger)
|
1784
|
1649
|
vhstr := string(vhtext)
|
1785
|
1650
|
|
1786
|
1651
|
key := fmt.Sprintf(keyAccountVHost, account)
|
1787
|
|
- queueKey := fmt.Sprintf(keyVHostQueueAcctToId, account)
|
1788
|
1652
|
err = am.server.store.Update(func(tx *buntdb.Tx) error {
|
1789
|
|
- if _, _, err := tx.Set(key, vhstr, nil); err != nil {
|
1790
|
|
- return err
|
1791
|
|
- }
|
1792
|
|
-
|
1793
|
|
- // update request queue
|
1794
|
|
- if clientAccount.VHost.RequestedVHost == "" && result.RequestedVHost != "" {
|
1795
|
|
- id := atomic.AddUint64(&am.vhostRequestID, 1)
|
1796
|
|
- if _, _, err = tx.Set(queueKey, strconv.FormatUint(id, 10), nil); err != nil {
|
1797
|
|
- return err
|
1798
|
|
- }
|
1799
|
|
- atomic.AddUint64(&am.vhostRequestPendingCount, 1)
|
1800
|
|
- } else if clientAccount.VHost.RequestedVHost != "" && result.RequestedVHost == "" {
|
1801
|
|
- _, err = tx.Delete(queueKey)
|
1802
|
|
- am.decrementVHostQueueCount(account, err)
|
1803
|
|
- }
|
1804
|
|
-
|
1805
|
|
- return nil
|
|
1653
|
+ _, _, err := tx.Set(key, vhstr, nil)
|
|
1654
|
+ return err
|
1806
|
1655
|
})
|
1807
|
1656
|
|
1808
|
1657
|
if err != nil {
|
|
@@ -1814,51 +1663,6 @@ func (am *AccountManager) performVHostChange(account string, munger vhostMunger)
|
1814
|
1663
|
return result, nil
|
1815
|
1664
|
}
|
1816
|
1665
|
|
1817
|
|
-// XXX annoying helper method for keeping the queue count in sync with the DB
|
1818
|
|
-// `err` is the buntdb error returned from deleting the queue key
|
1819
|
|
-func (am *AccountManager) decrementVHostQueueCount(account string, err error) {
|
1820
|
|
- if err == nil {
|
1821
|
|
- // successfully deleted a queue entry, do a 2's complement decrement:
|
1822
|
|
- atomic.AddUint64(&am.vhostRequestPendingCount, ^uint64(0))
|
1823
|
|
- } else if err != buntdb.ErrNotFound {
|
1824
|
|
- am.server.logger.Error("internal", "buntdb dequeue error", account, err.Error())
|
1825
|
|
- }
|
1826
|
|
-}
|
1827
|
|
-
|
1828
|
|
-func (am *AccountManager) VHostListRequests(limit int) (requests []PendingVHostRequest, total int) {
|
1829
|
|
- am.vHostUpdateMutex.Lock()
|
1830
|
|
- defer am.vHostUpdateMutex.Unlock()
|
1831
|
|
-
|
1832
|
|
- total = int(atomic.LoadUint64(&am.vhostRequestPendingCount))
|
1833
|
|
-
|
1834
|
|
- prefix := fmt.Sprintf(keyVHostQueueAcctToId, "")
|
1835
|
|
- accounts := make([]string, 0, limit)
|
1836
|
|
- err := am.server.store.View(func(tx *buntdb.Tx) error {
|
1837
|
|
- return tx.Ascend(vhostRequestIdx, func(key, value string) bool {
|
1838
|
|
- accounts = append(accounts, strings.TrimPrefix(key, prefix))
|
1839
|
|
- return len(accounts) < limit
|
1840
|
|
- })
|
1841
|
|
- })
|
1842
|
|
-
|
1843
|
|
- if err != nil {
|
1844
|
|
- am.server.logger.Error("internal", "couldn't traverse vhost queue", err.Error())
|
1845
|
|
- return
|
1846
|
|
- }
|
1847
|
|
-
|
1848
|
|
- for _, account := range accounts {
|
1849
|
|
- accountInfo, err := am.LoadAccount(account)
|
1850
|
|
- if err == nil {
|
1851
|
|
- requests = append(requests, PendingVHostRequest{
|
1852
|
|
- Account: account,
|
1853
|
|
- VHostInfo: accountInfo.VHost,
|
1854
|
|
- })
|
1855
|
|
- } else {
|
1856
|
|
- am.server.logger.Error("internal", "corrupt account", account, err.Error())
|
1857
|
|
- }
|
1858
|
|
- }
|
1859
|
|
- return
|
1860
|
|
-}
|
1861
|
|
-
|
1862
|
1666
|
func (am *AccountManager) applyVHostInfo(client *Client, info VHostInfo) {
|
1863
|
1667
|
// if hostserv is disabled in config, then don't grant vhosts
|
1864
|
1668
|
// that were previously approved while it was enabled
|