|
@@ -30,6 +30,11 @@ const (
|
30
|
30
|
// IdentTimeout is how long before our ident (username) check times out.
|
31
|
31
|
IdentTimeout = time.Second + 500*time.Millisecond
|
32
|
32
|
IRCv3TimestampFormat = utils.IRCv3TimestampFormat
|
|
33
|
+ // limit the number of device IDs a client can use, as a DoS mitigation
|
|
34
|
+ maxDeviceIDsPerClient = 64
|
|
35
|
+ // controls how often often we write an autoreplay-missed client's
|
|
36
|
+ // deviceid->lastseentime mapping to the database
|
|
37
|
+ lastSeenWriteInterval = time.Minute * 10
|
33
|
38
|
)
|
34
|
39
|
|
35
|
40
|
// ResumeDetails is a place to stash data at various stages of
|
|
@@ -60,8 +65,9 @@ type Client struct {
|
60
|
65
|
invitedTo map[string]bool
|
61
|
66
|
isSTSOnly bool
|
62
|
67
|
languages []string
|
63
|
|
- lastActive time.Time // last time they sent a command that wasn't PONG or similar
|
64
|
|
- lastSeen time.Time // last time they sent any kind of command
|
|
68
|
+ lastActive time.Time // last time they sent a command that wasn't PONG or similar
|
|
69
|
+ lastSeen map[string]time.Time // maps device ID (including "") to time of last received command
|
|
70
|
+ lastSeenLastWrite time.Time // last time `lastSeen` was written to the datastore
|
65
|
71
|
loginThrottle connection_limits.GenericThrottle
|
66
|
72
|
nick string
|
67
|
73
|
nickCasefolded string
|
|
@@ -112,6 +118,8 @@ const (
|
112
|
118
|
type Session struct {
|
113
|
119
|
client *Client
|
114
|
120
|
|
|
121
|
+ deviceID string
|
|
122
|
+
|
115
|
123
|
ctime time.Time
|
116
|
124
|
lastActive time.Time
|
117
|
125
|
|
|
@@ -299,7 +307,7 @@ func (server *Server) RunClient(conn IRCConn) {
|
299
|
307
|
// give them 1k of grace over the limit:
|
300
|
308
|
socket := NewSocket(conn, config.Server.MaxSendQBytes)
|
301
|
309
|
client := &Client{
|
302
|
|
- lastSeen: now,
|
|
310
|
+ lastSeen: make(map[string]time.Time),
|
303
|
311
|
lastActive: now,
|
304
|
312
|
channels: make(ChannelSet),
|
305
|
313
|
ctime: now,
|
|
@@ -358,11 +366,14 @@ func (server *Server) RunClient(conn IRCConn) {
|
358
|
366
|
client.run(session)
|
359
|
367
|
}
|
360
|
368
|
|
361
|
|
-func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSeen time.Time, uModes modes.Modes) {
|
|
369
|
+func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSeen map[string]time.Time, uModes modes.Modes) {
|
362
|
370
|
now := time.Now().UTC()
|
363
|
371
|
config := server.Config()
|
364
|
|
- if lastSeen.IsZero() {
|
365
|
|
- lastSeen = now
|
|
372
|
+ if lastSeen == nil {
|
|
373
|
+ lastSeen = make(map[string]time.Time)
|
|
374
|
+ if account.Settings.AutoreplayMissed {
|
|
375
|
+ lastSeen[""] = now
|
|
376
|
+ }
|
366
|
377
|
}
|
367
|
378
|
|
368
|
379
|
client := &Client{
|
|
@@ -714,14 +725,39 @@ func (client *Client) playReattachMessages(session *Session) {
|
714
|
725
|
// at this time, modulo network latency and fakelag). `active` means not a PING or suchlike
|
715
|
726
|
// (i.e. the user should be sitting in front of their client).
|
716
|
727
|
func (client *Client) Touch(active bool, session *Session) {
|
|
728
|
+ var markDirty bool
|
717
|
729
|
now := time.Now().UTC()
|
718
|
730
|
client.stateMutex.Lock()
|
719
|
|
- defer client.stateMutex.Unlock()
|
720
|
|
- client.lastSeen = now
|
721
|
731
|
if active {
|
722
|
732
|
client.lastActive = now
|
723
|
733
|
session.lastActive = now
|
724
|
734
|
}
|
|
735
|
+ if client.accountSettings.AutoreplayMissed {
|
|
736
|
+ client.setLastSeen(now, session.deviceID)
|
|
737
|
+ if now.Sub(client.lastSeenLastWrite) > lastSeenWriteInterval {
|
|
738
|
+ markDirty = true
|
|
739
|
+ client.lastSeenLastWrite = now
|
|
740
|
+ }
|
|
741
|
+ }
|
|
742
|
+ client.stateMutex.Unlock()
|
|
743
|
+ if markDirty {
|
|
744
|
+ client.markDirty(IncludeLastSeen)
|
|
745
|
+ }
|
|
746
|
+}
|
|
747
|
+
|
|
748
|
+func (client *Client) setLastSeen(now time.Time, deviceID string) {
|
|
749
|
+ client.lastSeen[deviceID] = now
|
|
750
|
+ // evict the least-recently-used entry if necessary
|
|
751
|
+ if maxDeviceIDsPerClient < len(client.lastSeen) {
|
|
752
|
+ var minLastSeen time.Time
|
|
753
|
+ var minClientId string
|
|
754
|
+ for deviceID, lastSeen := range client.lastSeen {
|
|
755
|
+ if minLastSeen.IsZero() || lastSeen.Before(minLastSeen) {
|
|
756
|
+ minClientId, minLastSeen = deviceID, lastSeen
|
|
757
|
+ }
|
|
758
|
+ }
|
|
759
|
+ delete(client.lastSeen, minClientId)
|
|
760
|
+ }
|
725
|
761
|
}
|
726
|
762
|
|
727
|
763
|
// Ping sends the client a PING message.
|
|
@@ -1604,6 +1640,12 @@ func (client *Client) attemptAutoOper(session *Session) {
|
1604
|
1640
|
}
|
1605
|
1641
|
}
|
1606
|
1642
|
|
|
1643
|
+func (client *Client) checkLoginThrottle() (throttled bool, remainingTime time.Duration) {
|
|
1644
|
+ client.stateMutex.Lock()
|
|
1645
|
+ defer client.stateMutex.Unlock()
|
|
1646
|
+ return client.loginThrottle.Touch()
|
|
1647
|
+}
|
|
1648
|
+
|
1607
|
1649
|
func (client *Client) historyStatus(config *Config) (status HistoryStatus, target string) {
|
1608
|
1650
|
if !config.History.Enabled {
|
1609
|
1651
|
return HistoryDisabled, ""
|
|
@@ -1624,6 +1666,16 @@ func (client *Client) historyStatus(config *Config) (status HistoryStatus, targe
|
1624
|
1666
|
return
|
1625
|
1667
|
}
|
1626
|
1668
|
|
|
1669
|
+func (client *Client) copyLastSeen() (result map[string]time.Time) {
|
|
1670
|
+ client.stateMutex.RLock()
|
|
1671
|
+ defer client.stateMutex.RUnlock()
|
|
1672
|
+ result = make(map[string]time.Time, len(client.lastSeen))
|
|
1673
|
+ for id, lastSeen := range client.lastSeen {
|
|
1674
|
+ result[id] = lastSeen
|
|
1675
|
+ }
|
|
1676
|
+ return
|
|
1677
|
+}
|
|
1678
|
+
|
1627
|
1679
|
// these are bit flags indicating what part of the client status is "dirty"
|
1628
|
1680
|
// and needs to be read from memory and written to the db
|
1629
|
1681
|
const (
|
|
@@ -1669,7 +1721,6 @@ func (client *Client) performWrite() {
|
1669
|
1721
|
dirtyBits := client.dirtyBits
|
1670
|
1722
|
client.dirtyBits = 0
|
1671
|
1723
|
account := client.account
|
1672
|
|
- lastSeen := client.lastSeen
|
1673
|
1724
|
client.stateMutex.Unlock()
|
1674
|
1725
|
|
1675
|
1726
|
if account == "" {
|
|
@@ -1686,7 +1737,7 @@ func (client *Client) performWrite() {
|
1686
|
1737
|
client.server.accounts.saveChannels(account, channelNames)
|
1687
|
1738
|
}
|
1688
|
1739
|
if (dirtyBits & IncludeLastSeen) != 0 {
|
1689
|
|
- client.server.accounts.saveLastSeen(account, lastSeen)
|
|
1740
|
+ client.server.accounts.saveLastSeen(account, client.copyLastSeen())
|
1690
|
1741
|
}
|
1691
|
1742
|
if (dirtyBits & IncludeUserModes) != 0 {
|
1692
|
1743
|
uModes := make(modes.Modes, 0, len(modes.SupportedUserModes))
|