From ba21987d03d7cd32b8cd8a0938e5c9b631a55574 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Tue, 18 May 2021 23:27:46 -0400 Subject: [PATCH 1/2] remove draft/resume-0.5 --- gencapdefs.py | 6 -- irc/caps/defs.go | 7 +- irc/channel.go | 74 ------------- irc/client.go | 217 +-------------------------------------- irc/client_lookup_set.go | 20 ---- irc/commands.go | 9 -- irc/config.go | 1 - irc/getters.go | 30 ------ irc/handlers.go | 58 ----------- irc/help.go | 14 --- irc/idletimer.go | 133 ------------------------ irc/numerics.go | 6 -- irc/resume.go | 104 ------------------- irc/server.go | 8 -- 14 files changed, 4 insertions(+), 683 deletions(-) delete mode 100644 irc/idletimer.go delete mode 100644 irc/resume.go diff --git a/gencapdefs.py b/gencapdefs.py index 26011fa1..985f2ac3 100644 --- a/gencapdefs.py +++ b/gencapdefs.py @@ -105,12 +105,6 @@ CAPDEFS = [ url="https://ircv3.net/specs/extensions/channel-rename", standard="draft IRCv3", ), - CapDef( - identifier="Resume", - name="draft/resume-0.5", - url="https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md", - standard="proposed IRCv3", - ), CapDef( identifier="SASL", name="sasl", diff --git a/irc/caps/defs.go b/irc/caps/defs.go index bc165ce7..4bfd3e29 100644 --- a/irc/caps/defs.go +++ b/irc/caps/defs.go @@ -7,7 +7,7 @@ package caps const ( // number of recognized capabilities: - numCapabs = 28 + numCapabs = 27 // length of the uint64 array that represents the bitset: bitsetLen = 1 ) @@ -65,10 +65,6 @@ const ( // https://github.com/ircv3/ircv3-specifications/pull/417 Relaymsg Capability = iota - // Resume is the proposed IRCv3 capability named "draft/resume-0.5": - // https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md - Resume Capability = iota - // EchoMessage is the IRCv3 capability named "echo-message": // https://ircv3.net/specs/extensions/echo-message-3.2.html EchoMessage Capability = iota @@ -142,7 +138,6 @@ var ( "draft/multiline", "draft/register", "draft/relaymsg", - "draft/resume-0.5", "echo-message", "extended-join", "invite-notify", diff --git a/irc/channel.go b/irc/channel.go index 00cfe868..22add543 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -1035,80 +1035,6 @@ func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer) client.server.logger.Debug("channels", fmt.Sprintf("%s left channel %s", details.nick, chname)) } -// Resume is called after a successful global resume to: -// 1. Replace the old client with the new in the channel's data structures -// 2. Send JOIN and MODE lines to channel participants (including the new client) -// 3. Replay missed message history to the client -func (channel *Channel) Resume(session *Session, timestamp time.Time) { - channel.resumeAndAnnounce(session) - if !timestamp.IsZero() { - channel.replayHistoryForResume(session, timestamp, time.Time{}) - } -} - -func (channel *Channel) resumeAndAnnounce(session *Session) { - channel.stateMutex.RLock() - memberData, found := channel.members[session.client] - channel.stateMutex.RUnlock() - if !found { - return - } - oldModes := memberData.modes.String() - if 0 < len(oldModes) { - oldModes = "+" + oldModes - } - - // send join for old clients - chname := channel.Name() - details := session.client.Details() - // TODO: for now, skip this entirely for auditoriums, - // but really we should send it to voiced clients - if !channel.flags.HasMode(modes.Auditorium) { - for _, member := range channel.Members() { - for _, mSes := range member.Sessions() { - if mSes == session || mSes.capabilities.Has(caps.Resume) { - continue - } - - if mSes.capabilities.Has(caps.ExtendedJoin) { - mSes.Send(nil, details.nickMask, "JOIN", chname, details.accountName, details.realname) - } else { - mSes.Send(nil, details.nickMask, "JOIN", chname) - } - - if 0 < len(oldModes) { - mSes.Send(nil, channel.server.name, "MODE", chname, oldModes, details.nick) - } - } - } - } - - rb := NewResponseBuffer(session) - // use blocking i/o to synchronize with the later history replay - if rb.session.capabilities.Has(caps.ExtendedJoin) { - rb.Add(nil, details.nickMask, "JOIN", channel.name, details.accountName, details.realname) - } else { - rb.Add(nil, details.nickMask, "JOIN", channel.name) - } - channel.SendTopic(session.client, rb, false) - channel.Names(session.client, rb) - rb.Send(true) -} - -func (channel *Channel) replayHistoryForResume(session *Session, after time.Time, before time.Time) { - var items []history.Item - afterS, beforeS := history.Selector{Time: after}, history.Selector{Time: before} - _, seq, _ := channel.server.GetHistorySequence(channel, session.client, "") - if seq != nil { - items, _ = seq.Between(afterS, beforeS, channel.server.Config().History.ZNCMax) - } - rb := NewResponseBuffer(session) - if len(items) != 0 { - channel.replayHistoryItems(rb, items, false) - } - rb.Send(true) -} - func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.Item, autoreplay bool) { // send an empty batch if necessary, as per the CHATHISTORY spec chname := channel.Name() diff --git a/irc/client.go b/irc/client.go index b127c3fc..e4669888 100644 --- a/irc/client.go +++ b/irc/client.go @@ -56,22 +56,11 @@ const ( // 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 // round off the ping interval by this much, see below: PingCoalesceThreshold = time.Second ) -// ResumeDetails is a place to stash data at various stages of -// the resume process: when handling the RESUME command itself, -// when completing the registration, and when rejoining channels. -type ResumeDetails struct { - PresentedToken string - Timestamp time.Time - HistoryIncomplete bool -} - // Client is an IRC client. type Client struct { account string @@ -79,7 +68,6 @@ type Client struct { accountRegDate time.Time accountSettings AccountSettings awayMessage string - brbTimer BrbTimer channels ChannelSet ctime time.Time destroyed bool @@ -109,7 +97,6 @@ type Client struct { registered bool registerCmdSent bool // already sent the draft/register command, can't send it again registrationTimer *time.Timer - resumeID string server *Server skeleton string sessions []*Session @@ -164,7 +151,6 @@ type Session struct { fakelag Fakelag deferredFakelagCount int - destroyed uint32 certfp string peerCerts []*x509.Certificate @@ -184,8 +170,6 @@ type Session struct { registrationMessages int - resumeID string - resumeDetails *ResumeDetails zncPlaybackTimes *zncPlaybackTimes autoreplayMissedSince time.Time @@ -259,20 +243,6 @@ func (s *Session) IP() net.IP { return s.realIP } -// returns whether the session was actively destroyed (for example, by ping -// timeout or NS GHOST). -// avoids a race condition between asynchronous idle-timing-out of sessions, -// and a condition that allows implicit BRB on connection errors (since -// destroy()'s socket.Close() appears to socket.Read() as a connection error) -func (session *Session) Destroyed() bool { - return atomic.LoadUint32(&session.destroyed) == 1 -} - -// sets the timed-out flag -func (session *Session) SetDestroyed() { - atomic.StoreUint32(&session.destroyed, 1) -} - // returns whether the client supports a smart history replay cap, // and therefore autoreplay-on-join and similar should be suppressed func (session *Session) HasHistoryCaps() bool { @@ -371,7 +341,6 @@ func (server *Server) RunClient(conn IRCConn) { client.requireSASLMessage = banMsg } client.history.Initialize(config.History.ClientLength, time.Duration(config.History.AutoresizeWindow)) - client.brbTimer.Initialize(client) session := &Session{ client: client, socket: socket, @@ -459,7 +428,6 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, channelToStatus m client.SetMode(m, true) } client.history.Initialize(0, 0) - client.brbTimer.Initialize(client) server.accounts.Login(client, account) @@ -553,7 +521,7 @@ func (client *Client) lookupHostname(session *Session, overwrite bool) { cloakedHostname := config.Server.Cloaks.ComputeCloak(ip) client.stateMutex.Lock() defer client.stateMutex.Unlock() - // update the hostname if this is a new connection or a resume, but not if it's a reattach + // update the hostname if this is a new connection, but not if it's a reattach if overwrite || client.rawHostname == "" { client.rawHostname = hostname client.cloakedHostname = cloakedHostname @@ -671,14 +639,7 @@ func (client *Client) run(session *Session) { isReattach := client.Registered() if isReattach { client.Touch(session) - if session.resumeDetails != nil { - session.playResume() - session.resumeDetails = nil - client.brbTimer.Disable() - session.SetAway("") // clear BRB message if any - } else { - client.playReattachMessages(session) - } + client.playReattachMessages(session) } firstLine := !isReattach @@ -697,11 +658,6 @@ func (client *Client) run(session *Session) { quitMessage = "connection closed" } client.Quit(quitMessage, session) - // since the client did not actually send us a QUIT, - // give them a chance to resume if applicable: - if !session.Destroyed() { - client.brbTimer.Enable() - } break } @@ -852,9 +808,6 @@ func (client *Client) updateIdleTimer(session *Session, now time.Time) { func (session *Session) handleIdleTimeout() { totalTimeout := DefaultTotalTimeout - if session.capabilities.Has(caps.Resume) { - totalTimeout = ResumeableTotalTimeout - } pingTimeout := DefaultIdleTimeout if session.isTor { pingTimeout = TorIdleTimeout @@ -911,151 +864,6 @@ func (session *Session) Ping() { session.Send(nil, "", "PING", session.client.Nick()) } -// tryResume tries to resume if the client asked us to. -func (session *Session) tryResume() (success bool) { - var oldResumeID string - - defer func() { - if success { - // "On a successful request, the server [...] terminates the old client's connection" - oldSession := session.client.GetSessionByResumeID(oldResumeID) - if oldSession != nil { - session.client.destroy(oldSession) - } - } else { - session.resumeDetails = nil - } - }() - - client := session.client - server := client.server - config := server.Config() - - oldClient, oldResumeID := server.resumeManager.VerifyToken(client, session.resumeDetails.PresentedToken) - if oldClient == nil { - session.Send(nil, server.name, "FAIL", "RESUME", "INVALID_TOKEN", client.t("Cannot resume connection, token is not valid")) - return - } - - resumeAllowed := config.Server.AllowPlaintextResume || (oldClient.HasMode(modes.TLS) && client.HasMode(modes.TLS)) - if !resumeAllowed { - session.Send(nil, server.name, "FAIL", "RESUME", "INSECURE_SESSION", client.t("Cannot resume connection, old and new clients must have TLS")) - return - } - - err := server.clients.Resume(oldClient, session) - if err != nil { - session.Send(nil, server.name, "FAIL", "RESUME", "CANNOT_RESUME", client.t("Cannot resume connection")) - return - } - - success = true - client.server.logger.Debug("quit", fmt.Sprintf("%s is being resumed", oldClient.Nick())) - - return -} - -// playResume is called from the session's fresh goroutine after a resume; -// it sends notifications to friends, then plays the registration burst and replays -// stored history to the session -func (session *Session) playResume() { - client := session.client - server := client.server - config := server.Config() - - friends := make(ClientSet) - var oldestLostMessage time.Time - - // work out how much time, if any, is not covered by history buffers - // assume that a persistent buffer covers the whole resume period - for _, channel := range client.Channels() { - for _, member := range channel.auditoriumFriends(client) { - friends.Add(member) - } - status, _, _ := channel.historyStatus(config) - if status == HistoryEphemeral { - lastDiscarded := channel.history.LastDiscarded() - if oldestLostMessage.Before(lastDiscarded) { - oldestLostMessage = lastDiscarded - } - } - } - cHistoryStatus, _ := client.historyStatus(config) - if cHistoryStatus == HistoryEphemeral { - lastDiscarded := client.history.LastDiscarded() - if oldestLostMessage.Before(lastDiscarded) { - oldestLostMessage = lastDiscarded - } - } - - timestamp := session.resumeDetails.Timestamp - gap := oldestLostMessage.Sub(timestamp) - session.resumeDetails.HistoryIncomplete = gap > 0 || timestamp.IsZero() - gapSeconds := int(gap.Seconds()) + 1 // round up to avoid confusion - - details := client.Details() - oldNickmask := details.nickMask - client.lookupHostname(session, true) - hostname := client.Hostname() // may be a vhost - timestampString := timestamp.Format(IRCv3TimestampFormat) - - // send quit/resume messages to friends - for friend := range friends { - if friend == client { - continue - } - for _, fSession := range friend.Sessions() { - if fSession.capabilities.Has(caps.Resume) { - if !session.resumeDetails.HistoryIncomplete { - fSession.Send(nil, oldNickmask, "RESUMED", hostname, "ok") - } else if session.resumeDetails.HistoryIncomplete && !timestamp.IsZero() { - fSession.Send(nil, oldNickmask, "RESUMED", hostname, timestampString) - } else { - fSession.Send(nil, oldNickmask, "RESUMED", hostname) - } - } else { - if !session.resumeDetails.HistoryIncomplete { - fSession.Send(nil, oldNickmask, "QUIT", friend.t("Client reconnected")) - } else if session.resumeDetails.HistoryIncomplete && !timestamp.IsZero() { - fSession.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected (up to %d seconds of message history lost)"), gapSeconds)) - } else { - fSession.Send(nil, oldNickmask, "QUIT", friend.t("Client reconnected (message history may have been lost)")) - } - } - } - } - - if session.resumeDetails.HistoryIncomplete { - if !timestamp.IsZero() { - session.Send(nil, client.server.name, "WARN", "RESUME", "HISTORY_LOST", fmt.Sprintf(client.t("Resume may have lost up to %d seconds of history"), gapSeconds)) - } else { - session.Send(nil, client.server.name, "WARN", "RESUME", "HISTORY_LOST", client.t("Resume may have lost some message history")) - } - } - - session.Send(nil, client.server.name, "RESUME", "SUCCESS", details.nick) - - server.playRegistrationBurst(session) - - for _, channel := range client.Channels() { - channel.Resume(session, timestamp) - } - - // replay direct PRIVSMG history - _, privmsgSeq, err := server.GetHistorySequence(nil, client, "") - if !timestamp.IsZero() && err == nil && privmsgSeq != nil { - after := history.Selector{Time: timestamp} - items, _ := privmsgSeq.Between(after, history.Selector{}, config.History.ZNCMax) - if len(items) != 0 { - rb := NewResponseBuffer(session) - client.replayPrivmsgHistory(rb, items, "") - rb.Send(true) - } - } - - session.resumeDetails = nil -} - func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.Item, target string) { var batchID string details := client.Details() @@ -1388,8 +1196,6 @@ func (client *Client) destroy(session *Session) { client.stateMutex.Lock() details := client.detailsNoMutex() - brbState := client.brbTimer.state - brbAt := client.brbTimer.brbAt wasReattach := session != nil && session.client != client sessionRemoved := false registered := client.registered @@ -1431,9 +1237,7 @@ func (client *Client) destroy(session *Session) { } // should we destroy the whole client this time? - // BRB is not respected if this is a destroy of the whole client (i.e., session == nil) - brbEligible := session != nil && brbState == BrbEnabled - shouldDestroy := !client.destroyed && remainingSessions == 0 && !brbEligible && !alwaysOn + shouldDestroy := !client.destroyed && remainingSessions == 0 && !alwaysOn // decrement stats on a true destroy, or for the removal of the last connected session // of an always-on client shouldDecrement := shouldDestroy || (alwaysOn && len(sessionsToDestroy) != 0 && len(client.sessions) == 0) @@ -1479,7 +1283,6 @@ func (client *Client) destroy(session *Session) { // send quit/error message to client if they haven't been sent already client.Quit("", session) quitMessage = session.quitMessage // doesn't need synch, we already detached - session.SetDestroyed() session.socket.Close() // clean up monitor state @@ -1538,8 +1341,6 @@ func (client *Client) destroy(session *Session) { client.server.whoWas.Append(client.WhoWas()) } - client.server.resumeManager.Delete(client) - // alert monitors if registered { client.server.monitorManager.AlertAbout(details.nick, details.nickCasefolded, false) @@ -1561,20 +1362,8 @@ func (client *Client) destroy(session *Session) { client.server.clients.Remove(client) // clean up self - client.brbTimer.Disable() - client.server.accounts.Logout(client) - // this happens under failure to return from BRB - if quitMessage == "" { - if brbState == BrbDead && !brbAt.IsZero() { - awayMessage := client.AwayMessage() - if awayMessage == "" { - awayMessage = "Disconnected" // auto-BRB - } - quitMessage = fmt.Sprintf("%s [%s ago]", awayMessage, time.Since(brbAt).Truncate(time.Second).String()) - } - } if quitMessage == "" { quitMessage = "Exited" } diff --git a/irc/client_lookup_set.go b/irc/client_lookup_set.go index 6992ee6e..2cc60318 100644 --- a/irc/client_lookup_set.go +++ b/irc/client_lookup_set.go @@ -81,26 +81,6 @@ func (clients *ClientManager) Remove(client *Client) error { return clients.removeInternal(client, oldcfnick, oldskeleton) } -// Handles a RESUME by attaching a session to a designated client. It is the -// caller's responsibility to verify that the resume is allowed (checking tokens, -// TLS status, etc.) before calling this. -func (clients *ClientManager) Resume(oldClient *Client, session *Session) (err error) { - clients.Lock() - defer clients.Unlock() - - cfnick := oldClient.NickCasefolded() - if _, ok := clients.byNick[cfnick]; !ok { - return errNickMissing - } - - success, _, _, _ := oldClient.AddSession(session) - if !success { - return errNickMissing - } - - return nil -} - // SetNick sets a client's nickname, validating it against nicknames in use // XXX: dryRun validates a client's ability to claim a nick, without // actually claiming it diff --git a/irc/commands.go b/irc/commands.go index 36cae878..59a46839 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -93,10 +93,6 @@ func init() { minParams: 1, allowedInBatch: true, }, - "BRB": { - handler: brbHandler, - minParams: 0, - }, "CAP": { handler: capHandler, usablePreReg: true, @@ -257,11 +253,6 @@ func init() { handler: renameHandler, minParams: 2, }, - "RESUME": { - handler: resumeHandler, - usablePreReg: true, - minParams: 1, - }, "SAJOIN": { handler: sajoinHandler, minParams: 1, diff --git a/irc/config.go b/irc/config.go index 3fab6fe2..bbe5de16 100644 --- a/irc/config.go +++ b/irc/config.go @@ -570,7 +570,6 @@ type Config struct { WebIRC []webircConfig `yaml:"webirc"` MaxSendQString string `yaml:"max-sendq"` MaxSendQBytes int - AllowPlaintextResume bool `yaml:"allow-plaintext-resume"` Compatibility struct { ForceTrailing *bool `yaml:"force-trailing"` forceTrailing bool diff --git a/irc/getters.go b/irc/getters.go index 2091e4af..d7c6dcaf 100644 --- a/irc/getters.go +++ b/irc/getters.go @@ -54,18 +54,6 @@ func (client *Client) Sessions() (sessions []*Session) { return } -func (client *Client) GetSessionByResumeID(resumeID string) (result *Session) { - client.stateMutex.RLock() - defer client.stateMutex.RUnlock() - - for _, session := range client.sessions { - if session.resumeID == resumeID { - return session - } - } - return -} - type SessionData struct { ctime time.Time atime time.Time @@ -157,12 +145,6 @@ func (client *Client) removeSession(session *Session) (success bool, length int) return } -func (session *Session) SetResumeID(resumeID string) { - session.client.stateMutex.Lock() - session.resumeID = resumeID - session.client.stateMutex.Unlock() -} - func (client *Client) Nick() string { client.stateMutex.RLock() defer client.stateMutex.RUnlock() @@ -265,18 +247,6 @@ func (client *Client) uniqueIdentifiers() (nickCasefolded string, skeleton strin return client.nickCasefolded, client.skeleton } -func (client *Client) ResumeID() string { - client.stateMutex.RLock() - defer client.stateMutex.RUnlock() - return client.resumeID -} - -func (client *Client) SetResumeID(id string) { - client.stateMutex.Lock() - defer client.stateMutex.Unlock() - client.resumeID = id -} - func (client *Client) Oper() *Oper { client.stateMutex.RLock() defer client.stateMutex.RUnlock() diff --git a/irc/handlers.go b/irc/handlers.go index 5f28816f..44cf8515 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -420,31 +420,6 @@ func batchHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respon return false } -// BRB [message] -func brbHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool { - success, duration := client.brbTimer.Enable() - if !success { - rb.Add(nil, server.name, "FAIL", "BRB", "CANNOT_BRB", client.t("Your client does not support BRB")) - return false - } else { - rb.Add(nil, server.name, "BRB", strconv.Itoa(int(duration.Seconds()))) - } - - var message string - if 0 < len(msg.Params) { - message = msg.Params[0] - } else { - message = client.t("I'll be right back") - } - - if len(client.Sessions()) == 1 { - // true BRB - rb.session.SetAway(message) - } - - return true -} - // CAP [] func capHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool { details := client.Details() @@ -540,15 +515,6 @@ func capHandler(server *Server, client *Client, msg ircmsg.Message, rb *Response rb.session.capabilities.Subtract(toRemove) rb.Add(nil, server.name, "CAP", details.nick, "ACK", capString) - // if this is the first time the client is requesting a resume token, - // send it to them - if toAdd.Has(caps.Resume) { - token, id := server.resumeManager.GenerateToken(client) - if token != "" { - rb.Add(nil, server.name, "RESUME", "TOKEN", token) - rb.session.SetResumeID(id) - } - } case "END": if !client.registered { rb.session.capState = caps.NegotiatedState @@ -2809,30 +2775,6 @@ func renameHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respo return false } -// RESUME [timestamp] -func resumeHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool { - details := ResumeDetails{ - PresentedToken: msg.Params[0], - } - - if client.registered { - rb.Add(nil, server.name, "FAIL", "RESUME", "REGISTRATION_IS_COMPLETED", client.t("Cannot resume connection, connection registration has already been completed")) - return false - } - - if 1 < len(msg.Params) { - ts, err := time.Parse(IRCv3TimestampFormat, msg.Params[1]) - if err == nil { - details.Timestamp = ts - } else { - rb.Add(nil, server.name, "WARN", "RESUME", "HISTORY_LOST", client.t("Timestamp is not in 2006-01-02T15:04:05.999Z format, ignoring it")) - } - } - - rb.session.resumeDetails = &details - return false -} - // SANICK func sanickHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool { targetNick := msg.Params[0] diff --git a/irc/help.go b/irc/help.go index 5fa5fca2..f750f86f 100644 --- a/irc/help.go +++ b/irc/help.go @@ -129,14 +129,6 @@ longer away.`, BATCH initiates an IRCv3 client-to-server batch. You should never need to issue this command manually.`, - }, - "brb": { - text: `BRB [message] - -Disconnects you from the server, while instructing the server to keep you -present for a short time window. During this window, you can either resume -or reattach to your nickname. If [message] is sent, it is used as your away -message (and as your quit message if you don't return in time).`, }, "cap": { text: `CAP [:] @@ -495,12 +487,6 @@ Registers an account in accordance with the draft/register capability.`, text: `REHASH Reloads the config file and updates TLS certificates on listeners`, - }, - "resume": { - text: `RESUME [timestamp] - -Sent before registration has completed, this indicates that the client wants to -resume their old connection .`, }, "time": { text: `TIME [server] diff --git a/irc/idletimer.go b/irc/idletimer.go deleted file mode 100644 index a9f07f91..00000000 --- a/irc/idletimer.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2017 Shivaram Lingamneni -// released under the MIT license - -package irc - -import ( - "time" -) - -// BrbTimer is a timer on the client as a whole (not an individual session) for implementing -// the BRB command and related functionality (where a client can remain online without -// having any connected sessions). - -type BrbState uint - -const ( - // BrbDisabled is the default state; the client will be disconnected if it has no sessions - BrbDisabled BrbState = iota - // BrbEnabled allows the client to remain online without sessions; if a timeout is - // reached, it will be removed - BrbEnabled - // BrbDead is the state of a client after its timeout has expired; it will be removed - // and therefore new sessions cannot be attached to it - BrbDead -) - -type BrbTimer struct { - // XXX we use client.stateMutex for synchronization, so we can atomically test - // conditions that use both brbTimer.state and client.sessions. This code - // is tightly coupled with the rest of Client. - client *Client - - state BrbState - brbAt time.Time - duration time.Duration - timer *time.Timer -} - -func (bt *BrbTimer) Initialize(client *Client) { - bt.client = client -} - -// attempts to enable BRB for a client, returns whether it succeeded -func (bt *BrbTimer) Enable() (success bool, duration time.Duration) { - // TODO make this configurable - duration = ResumeableTotalTimeout - - bt.client.stateMutex.Lock() - defer bt.client.stateMutex.Unlock() - - if !bt.client.registered || bt.client.alwaysOn || bt.client.resumeID == "" { - return - } - - switch bt.state { - case BrbDisabled, BrbEnabled: - bt.state = BrbEnabled - bt.duration = duration - bt.resetTimeout() - // only track the earliest BRB, if multiple sessions are BRB'ing at once - // TODO(#524) this is inaccurate in case of an auto-BRB - if bt.brbAt.IsZero() { - bt.brbAt = time.Now().UTC() - } - success = true - default: - // BrbDead - success = false - } - return -} - -// turns off BRB for a client and stops the timer; used on resume and during -// client teardown -func (bt *BrbTimer) Disable() (brbAt time.Time) { - bt.client.stateMutex.Lock() - defer bt.client.stateMutex.Unlock() - - if bt.state == BrbEnabled { - bt.state = BrbDisabled - brbAt = bt.brbAt - bt.brbAt = time.Time{} - } - bt.resetTimeout() - return -} - -func (bt *BrbTimer) resetTimeout() { - if bt.timer != nil { - bt.timer.Stop() - } - if bt.state != BrbEnabled { - return - } - if bt.timer == nil { - bt.timer = time.AfterFunc(bt.duration, bt.processTimeout) - } else { - bt.timer.Reset(bt.duration) - } -} - -func (bt *BrbTimer) processTimeout() { - dead := false - defer func() { - if dead { - bt.client.Quit(bt.client.AwayMessage(), nil) - bt.client.destroy(nil) - } - }() - - bt.client.stateMutex.Lock() - defer bt.client.stateMutex.Unlock() - - if bt.client.alwaysOn { - return - } - - switch bt.state { - case BrbDisabled, BrbEnabled: - if len(bt.client.sessions) == 0 { - // client never returned, quit them - bt.state = BrbDead - dead = true - } else { - // client resumed, reattached, or has another active session - bt.state = BrbDisabled - bt.brbAt = time.Time{} - } - case BrbDead: - dead = true // shouldn't be possible but whatever - } - bt.resetTimeout() -} diff --git a/irc/numerics.go b/irc/numerics.go index 01b22499..6e4cffd3 100644 --- a/irc/numerics.go +++ b/irc/numerics.go @@ -196,10 +196,4 @@ const ( RPL_REG_VERIFICATION_REQUIRED = "927" ERR_TOOMANYLANGUAGES = "981" ERR_NOLANGUAGE = "982" - - // draft numerics - // these haven't been assigned actual codes, so we use RPL_NONE's code (300), - // since RPL_NONE is intended to be used when testing / debugging / etc features. - - ERR_CANNOT_RESUME = "300" ) diff --git a/irc/resume.go b/irc/resume.go deleted file mode 100644 index 64c1764c..00000000 --- a/irc/resume.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) 2019 Shivaram Lingamneni -// released under the MIT license - -package irc - -import ( - "sync" - - "github.com/oragono/oragono/irc/utils" -) - -// implements draft/resume, in particular the issuing, management, and verification -// of resume tokens with two components: a unique ID and a secret key - -type resumeTokenPair struct { - client *Client - secret string -} - -type ResumeManager struct { - sync.Mutex // level 2 - - resumeIDtoCreds map[string]resumeTokenPair - server *Server -} - -func (rm *ResumeManager) Initialize(server *Server) { - rm.resumeIDtoCreds = make(map[string]resumeTokenPair) - rm.server = server -} - -// GenerateToken generates a resume token for a client. If the client has -// already been assigned one, it returns "". -func (rm *ResumeManager) GenerateToken(client *Client) (token string, id string) { - id = utils.GenerateSecretToken() - secret := utils.GenerateSecretToken() - - rm.Lock() - defer rm.Unlock() - - if client.ResumeID() != "" { - return - } - - client.SetResumeID(id) - rm.resumeIDtoCreds[id] = resumeTokenPair{ - client: client, - secret: secret, - } - - return id + secret, id -} - -// VerifyToken looks up the client corresponding to a resume token, returning -// nil if there is no such client or the token is invalid. If successful, -// the token is consumed and cannot be used to resume again. -func (rm *ResumeManager) VerifyToken(newClient *Client, token string) (oldClient *Client, id string) { - if len(token) != 2*utils.SecretTokenLength { - return - } - - rm.Lock() - defer rm.Unlock() - - id = token[:utils.SecretTokenLength] - pair, ok := rm.resumeIDtoCreds[id] - if !ok { - return - } - // disallow resume of an unregistered client; this prevents the use of - // resume as an auth bypass - if !pair.client.Registered() { - return - } - - if utils.SecretTokensMatch(pair.secret, token[utils.SecretTokenLength:]) { - oldClient = pair.client // success! - // consume the token, ensuring that at most one resume can succeed - delete(rm.resumeIDtoCreds, id) - // old client is henceforth resumeable under new client's creds (possibly empty) - newResumeID := newClient.ResumeID() - oldClient.SetResumeID(newResumeID) - if newResumeID != "" { - if newResumeCreds, ok := rm.resumeIDtoCreds[newResumeID]; ok { - newResumeCreds.client = oldClient - rm.resumeIDtoCreds[newResumeID] = newResumeCreds - } - } - // new client no longer "owns" newResumeID, remove the association - newClient.SetResumeID("") - } - return -} - -// Delete stops tracking a client's resume token. -func (rm *ResumeManager) Delete(client *Client) { - rm.Lock() - defer rm.Unlock() - - currentID := client.ResumeID() - if currentID != "" { - delete(rm.resumeIDtoCreds, currentID) - } -} diff --git a/irc/server.go b/irc/server.go index 3f05060c..280e7557 100644 --- a/irc/server.go +++ b/irc/server.go @@ -80,7 +80,6 @@ type Server struct { rehashMutex sync.Mutex // tier 4 rehashSignal chan os.Signal pprofServer *http.Server - resumeManager ResumeManager signals chan os.Signal snomasks SnoManager store *buntdb.DB @@ -106,7 +105,6 @@ func NewServer(config *Config, logger *logger.Manager) (*Server, error) { server.clients.Initialize() server.semaphores.Initialize() - server.resumeManager.Initialize(server) server.whoWas.Initialize(config.Limits.WhowasEntries) server.monitorManager.Initialize() server.snomasks.Initialize() @@ -273,12 +271,6 @@ func (server *Server) handleAlwaysOnExpirations() { // func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) { - // if the session just sent us a RESUME line, try to resume - if session.resumeDetails != nil { - session.tryResume() - return // whether we succeeded or failed, either way `c` is not getting registered - } - // XXX PROXY or WEBIRC MUST be sent as the first line of the session; // if we are here at all that means we have the final value of the IP if session.rawHostname == "" { From 56a0407ff5efee3125230dcece38e470c6d66fab Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Tue, 18 May 2021 23:28:13 -0400 Subject: [PATCH 2/2] bump irctest to remove draft/resume-0.5 tests --- irctest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irctest b/irctest index 322cb7ae..f3b53b00 160000 --- a/irctest +++ b/irctest @@ -1 +1 @@ -Subproject commit 322cb7ae26a2a94a0daec5458373319fa4e0e743 +Subproject commit f3b53b00483e0504bdc6bca5701fa98025ea8acf