2017-10-15 18:24:28 +02:00
|
|
|
// Copyright (c) 2017 Shivaram Lingamneni <slingamn@cs.stanford.edu>
|
|
|
|
// released under the MIT license
|
|
|
|
|
|
|
|
package irc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"sync"
|
|
|
|
"time"
|
2018-01-21 02:23:33 +01:00
|
|
|
|
2019-02-18 04:59:13 +01:00
|
|
|
"github.com/goshuirc/irc-go/ircfmt"
|
2018-01-21 02:23:33 +01:00
|
|
|
"github.com/oragono/oragono/irc/caps"
|
2017-10-15 18:24:28 +02:00
|
|
|
)
|
|
|
|
|
2017-10-16 04:37:36 +02:00
|
|
|
const (
|
|
|
|
// RegisterTimeout is how long clients have to register before we disconnect them
|
|
|
|
RegisterTimeout = time.Minute
|
2019-03-06 03:57:17 +01:00
|
|
|
// DefaultIdleTimeout is how long without traffic before we send the client a PING
|
|
|
|
DefaultIdleTimeout = time.Minute + 30*time.Second
|
|
|
|
// For Tor clients, we send a PING at least every 30 seconds, as a workaround for this bug
|
|
|
|
// (single-onion circuits will close unless the client sends data once every 60 seconds):
|
|
|
|
// https://bugs.torproject.org/29665
|
|
|
|
TorIdleTimeout = time.Second * 30
|
|
|
|
// This is how long a client gets without sending any message, including the PONG to our
|
|
|
|
// PING, before we disconnect them:
|
|
|
|
DefaultTotalTimeout = 2*time.Minute + 30*time.Second
|
|
|
|
// Resumeable clients (clients who have negotiated caps.Resume) get longer:
|
|
|
|
ResumeableTotalTimeout = 3*time.Minute + 30*time.Second
|
2017-10-16 04:37:36 +02:00
|
|
|
)
|
|
|
|
|
2017-10-15 18:24:28 +02:00
|
|
|
// client idleness state machine
|
|
|
|
|
|
|
|
type TimerState uint
|
|
|
|
|
|
|
|
const (
|
|
|
|
TimerUnregistered TimerState = iota // client is unregistered
|
|
|
|
TimerActive // client is actively sending commands
|
|
|
|
TimerIdle // client is idle, we sent PING and are waiting for PONG
|
2017-12-07 05:15:35 +01:00
|
|
|
TimerDead // client was terminated
|
2017-10-15 18:24:28 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type IdleTimer struct {
|
2017-11-22 10:41:11 +01:00
|
|
|
sync.Mutex // tier 1
|
2017-10-15 18:24:28 +02:00
|
|
|
|
|
|
|
// immutable after construction
|
2018-01-30 05:26:29 +01:00
|
|
|
registerTimeout time.Duration
|
|
|
|
client *Client
|
2017-10-15 18:24:28 +02:00
|
|
|
|
|
|
|
// mutable
|
2018-01-30 05:26:29 +01:00
|
|
|
idleTimeout time.Duration
|
2019-03-06 03:57:17 +01:00
|
|
|
quitTimeout time.Duration
|
2018-01-30 05:26:29 +01:00
|
|
|
state TimerState
|
|
|
|
timer *time.Timer
|
2017-10-15 18:24:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewIdleTimer sets up a new IdleTimer using constant timeouts.
|
|
|
|
func NewIdleTimer(client *Client) *IdleTimer {
|
|
|
|
it := IdleTimer{
|
2018-01-30 05:26:29 +01:00
|
|
|
registerTimeout: RegisterTimeout,
|
|
|
|
client: client,
|
2017-10-15 18:24:28 +02:00
|
|
|
}
|
2019-03-06 03:57:17 +01:00
|
|
|
it.idleTimeout, it.quitTimeout = it.recomputeDurations()
|
2017-10-15 18:24:28 +02:00
|
|
|
return &it
|
|
|
|
}
|
|
|
|
|
2019-03-06 03:57:17 +01:00
|
|
|
// recomputeDurations recomputes the idle and quit durations, given the client's caps.
|
|
|
|
func (it *IdleTimer) recomputeDurations() (idleTimeout, quitTimeout time.Duration) {
|
|
|
|
totalTimeout := DefaultTotalTimeout
|
2018-01-30 05:26:29 +01:00
|
|
|
// if they have the resume cap, wait longer before pinging them out
|
|
|
|
// to give them a chance to resume their connection
|
|
|
|
if it.client.capabilities.Has(caps.Resume) {
|
2019-03-06 03:57:17 +01:00
|
|
|
totalTimeout = ResumeableTotalTimeout
|
2018-01-30 05:26:29 +01:00
|
|
|
}
|
|
|
|
|
2019-03-06 03:57:17 +01:00
|
|
|
idleTimeout = DefaultIdleTimeout
|
|
|
|
if it.client.isTor {
|
|
|
|
idleTimeout = TorIdleTimeout
|
|
|
|
}
|
|
|
|
|
|
|
|
quitTimeout = totalTimeout - idleTimeout
|
|
|
|
return
|
2018-01-30 05:26:29 +01:00
|
|
|
}
|
|
|
|
|
2017-10-15 18:24:28 +02:00
|
|
|
// Start starts counting idle time; if there is no activity from the client,
|
|
|
|
// it will eventually be stopped.
|
|
|
|
func (it *IdleTimer) Start() {
|
|
|
|
it.Lock()
|
2017-12-07 05:15:35 +01:00
|
|
|
defer it.Unlock()
|
|
|
|
it.state = TimerUnregistered
|
|
|
|
it.resetTimeout()
|
2017-10-15 18:24:28 +02:00
|
|
|
}
|
|
|
|
|
2017-12-07 05:15:35 +01:00
|
|
|
func (it *IdleTimer) Touch() {
|
2019-03-06 03:57:17 +01:00
|
|
|
idleTimeout, quitTimeout := it.recomputeDurations()
|
2018-01-30 05:26:29 +01:00
|
|
|
|
2017-12-07 05:15:35 +01:00
|
|
|
it.Lock()
|
|
|
|
defer it.Unlock()
|
2019-03-06 03:57:17 +01:00
|
|
|
it.idleTimeout, it.quitTimeout = idleTimeout, quitTimeout
|
2017-12-07 05:15:35 +01:00
|
|
|
// a touch transitions TimerUnregistered or TimerIdle into TimerActive
|
|
|
|
if it.state != TimerDead {
|
|
|
|
it.state = TimerActive
|
|
|
|
it.resetTimeout()
|
2017-10-15 18:24:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-07 05:15:35 +01:00
|
|
|
func (it *IdleTimer) processTimeout() {
|
2019-03-06 03:57:17 +01:00
|
|
|
idleTimeout, quitTimeout := it.recomputeDurations()
|
2018-01-30 05:26:29 +01:00
|
|
|
|
2017-12-07 05:15:35 +01:00
|
|
|
var previousState TimerState
|
|
|
|
func() {
|
|
|
|
it.Lock()
|
|
|
|
defer it.Unlock()
|
2019-03-06 03:57:17 +01:00
|
|
|
it.idleTimeout, it.quitTimeout = idleTimeout, quitTimeout
|
2017-12-07 05:15:35 +01:00
|
|
|
previousState = it.state
|
|
|
|
// TimerActive transitions to TimerIdle, all others to TimerDead
|
|
|
|
if it.state == TimerActive {
|
|
|
|
// send them a ping, give them time to respond
|
|
|
|
it.state = TimerIdle
|
|
|
|
it.resetTimeout()
|
|
|
|
} else {
|
|
|
|
it.state = TimerDead
|
|
|
|
}
|
|
|
|
}()
|
2017-10-15 18:24:28 +02:00
|
|
|
|
2017-12-07 05:15:35 +01:00
|
|
|
if previousState == TimerActive {
|
|
|
|
it.client.Ping()
|
|
|
|
} else {
|
|
|
|
it.client.Quit(it.quitMessage(previousState))
|
2018-01-21 02:59:52 +01:00
|
|
|
it.client.destroy(false)
|
2017-10-15 18:24:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop stops counting idle time.
|
|
|
|
func (it *IdleTimer) Stop() {
|
2018-02-28 06:14:44 +01:00
|
|
|
if it == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-10-15 18:24:28 +02:00
|
|
|
it.Lock()
|
|
|
|
defer it.Unlock()
|
2017-12-07 05:15:35 +01:00
|
|
|
it.state = TimerDead
|
|
|
|
it.resetTimeout()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (it *IdleTimer) resetTimeout() {
|
|
|
|
if it.timer != nil {
|
|
|
|
it.timer.Stop()
|
|
|
|
}
|
|
|
|
var nextTimeout time.Duration
|
|
|
|
switch it.state {
|
|
|
|
case TimerUnregistered:
|
|
|
|
nextTimeout = it.registerTimeout
|
|
|
|
case TimerActive:
|
2018-01-30 05:26:29 +01:00
|
|
|
nextTimeout = it.idleTimeout
|
2017-12-07 05:15:35 +01:00
|
|
|
case TimerIdle:
|
|
|
|
nextTimeout = it.quitTimeout
|
|
|
|
case TimerDead:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
it.timer = time.AfterFunc(nextTimeout, it.processTimeout)
|
2017-10-15 18:24:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (it *IdleTimer) quitMessage(state TimerState) string {
|
|
|
|
switch state {
|
|
|
|
case TimerUnregistered:
|
|
|
|
return fmt.Sprintf("Registration timeout: %v", it.registerTimeout)
|
|
|
|
case TimerIdle:
|
|
|
|
// how many seconds before registered clients are timed out (IdleTimeout plus QuitTimeout).
|
2018-01-30 05:26:29 +01:00
|
|
|
it.Lock()
|
|
|
|
defer it.Unlock()
|
2017-10-15 18:24:28 +02:00
|
|
|
return fmt.Sprintf("Ping timeout: %v", (it.idleTimeout + it.quitTimeout))
|
|
|
|
default:
|
|
|
|
// shouldn't happen
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
}
|
2018-02-11 11:30:40 +01:00
|
|
|
|
|
|
|
// NickTimer manages timing out of clients who are squatting reserved nicks
|
|
|
|
type NickTimer struct {
|
|
|
|
sync.Mutex // tier 1
|
|
|
|
|
|
|
|
// immutable after construction
|
|
|
|
timeout time.Duration
|
|
|
|
client *Client
|
|
|
|
|
|
|
|
// mutable
|
2018-03-14 13:43:50 +01:00
|
|
|
stopped bool
|
2018-02-11 11:30:40 +01:00
|
|
|
nick string
|
|
|
|
accountForNick string
|
|
|
|
account string
|
|
|
|
timer *time.Timer
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewNickTimer sets up a new nick timer (returning nil if timeout enforcement is not enabled)
|
|
|
|
func NewNickTimer(client *Client) *NickTimer {
|
2018-02-18 10:46:14 +01:00
|
|
|
config := client.server.AccountConfig().NickReservation
|
2019-01-02 16:08:44 +01:00
|
|
|
if !(config.Enabled && (config.Method == NickReservationWithTimeout || config.AllowCustomEnforcement)) {
|
2018-02-11 11:30:40 +01:00
|
|
|
return nil
|
|
|
|
}
|
2019-01-02 16:08:44 +01:00
|
|
|
|
|
|
|
return &NickTimer{
|
2018-02-11 11:30:40 +01:00
|
|
|
client: client,
|
2018-02-18 10:46:14 +01:00
|
|
|
timeout: config.RenameTimeout,
|
2018-02-11 11:30:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Touch records a nick change and updates the timer as necessary
|
|
|
|
func (nt *NickTimer) Touch() {
|
|
|
|
if nt == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-01-31 00:59:49 +01:00
|
|
|
cfnick, skeleton := nt.client.uniqueIdentifiers()
|
2018-02-11 11:30:40 +01:00
|
|
|
account := nt.client.Account()
|
2019-01-31 00:59:49 +01:00
|
|
|
accountForNick, method := nt.client.server.accounts.EnforcementStatus(cfnick, skeleton)
|
2019-01-02 16:08:44 +01:00
|
|
|
enforceTimeout := method == NickReservationWithTimeout
|
2018-02-11 11:30:40 +01:00
|
|
|
|
|
|
|
var shouldWarn bool
|
|
|
|
|
|
|
|
func() {
|
|
|
|
nt.Lock()
|
|
|
|
defer nt.Unlock()
|
2018-03-14 13:43:50 +01:00
|
|
|
|
|
|
|
if nt.stopped {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-02-11 11:30:40 +01:00
|
|
|
// the timer will not reset as long as the squatter is targeting the same account
|
|
|
|
accountChanged := accountForNick != nt.accountForNick
|
|
|
|
// change state
|
2019-01-31 00:59:49 +01:00
|
|
|
nt.nick = cfnick
|
2018-02-11 11:30:40 +01:00
|
|
|
nt.account = account
|
|
|
|
nt.accountForNick = accountForNick
|
|
|
|
delinquent := accountForNick != "" && accountForNick != account
|
|
|
|
|
2019-01-02 16:08:44 +01:00
|
|
|
if nt.timer != nil && (!enforceTimeout || !delinquent || accountChanged) {
|
2018-02-11 11:30:40 +01:00
|
|
|
nt.timer.Stop()
|
|
|
|
nt.timer = nil
|
|
|
|
}
|
2019-01-02 16:08:44 +01:00
|
|
|
if enforceTimeout && delinquent && accountChanged {
|
2018-02-11 11:30:40 +01:00
|
|
|
nt.timer = time.AfterFunc(nt.timeout, nt.processTimeout)
|
|
|
|
shouldWarn = true
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
if shouldWarn {
|
|
|
|
nt.sendWarning()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-28 06:14:44 +01:00
|
|
|
// Stop stops counting time and cleans up the timer
|
|
|
|
func (nt *NickTimer) Stop() {
|
|
|
|
if nt == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
nt.Lock()
|
|
|
|
defer nt.Unlock()
|
|
|
|
if nt.timer != nil {
|
|
|
|
nt.timer.Stop()
|
|
|
|
nt.timer = nil
|
|
|
|
}
|
2018-03-14 13:43:50 +01:00
|
|
|
nt.stopped = true
|
2018-02-28 06:14:44 +01:00
|
|
|
}
|
|
|
|
|
2018-02-11 11:30:40 +01:00
|
|
|
func (nt *NickTimer) sendWarning() {
|
2019-02-18 04:59:13 +01:00
|
|
|
nt.client.Send(nil, "NickServ", "NOTICE", nt.client.Nick(), fmt.Sprintf(ircfmt.Unescape(nt.client.t(nsTimeoutNotice)), nt.timeout))
|
2018-02-11 11:30:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (nt *NickTimer) processTimeout() {
|
|
|
|
baseMsg := "Nick is reserved and authentication timeout expired: %v"
|
2018-02-18 10:46:14 +01:00
|
|
|
nt.client.Notice(fmt.Sprintf(nt.client.t(baseMsg), nt.timeout))
|
|
|
|
nt.client.server.RandomlyRename(nt.client)
|
2018-02-11 11:30:40 +01:00
|
|
|
}
|