Procházet zdrojové kódy

implement draft/resume-0.4

tags/v1.1.0-rc1
Shivaram Lingamneni před 5 roky
rodič
revize
3d445573cf
15 změnil soubory, kde provedl 441 přidání a 342 odebrání
  1. 1
    1
      gencapdefs.py
  2. 1
    1
      irc/accounts.go
  3. 2
    2
      irc/caps/defs.go
  4. 35
    50
      irc/channel.go
  5. 107
    161
      irc/client.go
  6. 10
    36
      irc/client_lookup_set.go
  7. 4
    0
      irc/commands.go
  8. 35
    1
      irc/getters.go
  9. 40
    17
      irc/handlers.go
  10. 8
    0
      irc/help.go
  11. 137
    2
      irc/idletimer.go
  12. 0
    18
      irc/monitor.go
  13. 1
    1
      irc/nickserv.go
  14. 28
    14
      irc/resume.go
  15. 32
    38
      irc/server.go

+ 1
- 1
gencapdefs.py Zobrazit soubor

@@ -113,7 +113,7 @@ CAPDEFS = [
113 113
     ),
114 114
     CapDef(
115 115
         identifier="Resume",
116
-        name="draft/resume-0.3",
116
+        name="draft/resume-0.4",
117 117
         url="https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md",
118 118
         standard="proposed IRCv3",
119 119
     ),

+ 1
- 1
irc/accounts.go Zobrazit soubor

@@ -931,7 +931,7 @@ func (am *AccountManager) Unregister(account string) error {
931 931
 		if config.Accounts.RequireSasl.Enabled {
932 932
 			client.Quit(client.t("You are no longer authorized to be on this server"), nil)
933 933
 			// destroy acquires a semaphore so we can't call it while holding a lock
934
-			go client.destroy(false, nil)
934
+			go client.destroy(nil)
935 935
 		} else {
936 936
 			am.logoutOfAccount(client)
937 937
 		}

+ 2
- 2
irc/caps/defs.go Zobrazit soubor

@@ -77,7 +77,7 @@ const (
77 77
 	// https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md
78 78
 	Rename Capability = iota
79 79
 
80
-	// Resume is the proposed IRCv3 capability named "draft/resume-0.3":
80
+	// Resume is the proposed IRCv3 capability named "draft/resume-0.4":
81 81
 	// https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md
82 82
 	Resume Capability = iota
83 83
 
@@ -133,7 +133,7 @@ var (
133 133
 		"message-tags",
134 134
 		"multi-prefix",
135 135
 		"draft/rename",
136
-		"draft/resume-0.3",
136
+		"draft/resume-0.4",
137 137
 		"sasl",
138 138
 		"server-time",
139 139
 		"draft/setname",

+ 35
- 50
irc/channel.go Zobrazit soubor

@@ -20,6 +20,10 @@ import (
20 20
 	"github.com/oragono/oragono/irc/utils"
21 21
 )
22 22
 
23
+const (
24
+	histServMask = "HistServ!HistServ@localhost"
25
+)
26
+
23 27
 // Channel represents a channel that clients can join.
24 28
 type Channel struct {
25 29
 	flags             modes.ModeSet
@@ -695,46 +699,30 @@ func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer)
695 699
 // 1. Replace the old client with the new in the channel's data structures
696 700
 // 2. Send JOIN and MODE lines to channel participants (including the new client)
697 701
 // 3. Replay missed message history to the client
698
-func (channel *Channel) Resume(newClient, oldClient *Client, timestamp time.Time) {
702
+func (channel *Channel) Resume(session *Session, timestamp time.Time) {
699 703
 	now := time.Now().UTC()
700
-	channel.resumeAndAnnounce(newClient, oldClient)
704
+	channel.resumeAndAnnounce(session)
701 705
 	if !timestamp.IsZero() {
702
-		channel.replayHistoryForResume(newClient, timestamp, now)
706
+		channel.replayHistoryForResume(session, timestamp, now)
703 707
 	}
704 708
 }
705 709
 
706
-func (channel *Channel) resumeAndAnnounce(newClient, oldClient *Client) {
707
-	var oldModeSet *modes.ModeSet
708
-
709
-	func() {
710
-		channel.joinPartMutex.Lock()
711
-		defer channel.joinPartMutex.Unlock()
712
-
713
-		defer channel.regenerateMembersCache()
714
-
715
-		channel.stateMutex.Lock()
716
-		defer channel.stateMutex.Unlock()
717
-
718
-		newClient.channels[channel] = true
719
-		oldModeSet = channel.members[oldClient]
720
-		if oldModeSet == nil {
721
-			oldModeSet = modes.NewModeSet()
722
-		}
723
-		channel.members.Remove(oldClient)
724
-		channel.members[newClient] = oldModeSet
725
-	}()
726
-
727
-	// construct fake modestring if necessary
728
-	oldModes := oldModeSet.String()
710
+func (channel *Channel) resumeAndAnnounce(session *Session) {
711
+	channel.stateMutex.RLock()
712
+	modeSet := channel.members[session.client]
713
+	channel.stateMutex.RUnlock()
714
+	if modeSet == nil {
715
+		return
716
+	}
717
+	oldModes := modeSet.String()
729 718
 	if 0 < len(oldModes) {
730 719
 		oldModes = "+" + oldModes
731 720
 	}
732 721
 
733 722
 	// send join for old clients
734
-	nick := newClient.Nick()
735
-	nickMask := newClient.NickMaskString()
736
-	accountName := newClient.AccountName()
737
-	realName := newClient.Realname()
723
+	chname := channel.Name()
724
+	details := session.client.Details()
725
+	realName := session.client.Realname()
738 726
 	for _, member := range channel.Members() {
739 727
 		for _, session := range member.Sessions() {
740 728
 			if session.capabilities.Has(caps.Resume) {
@@ -742,39 +730,36 @@ func (channel *Channel) resumeAndAnnounce(newClient, oldClient *Client) {
742 730
 			}
743 731
 
744 732
 			if session.capabilities.Has(caps.ExtendedJoin) {
745
-				session.Send(nil, nickMask, "JOIN", channel.name, accountName, realName)
733
+				session.Send(nil, details.nickMask, "JOIN", chname, details.accountName, realName)
746 734
 			} else {
747
-				session.Send(nil, nickMask, "JOIN", channel.name)
735
+				session.Send(nil, details.nickMask, "JOIN", chname)
748 736
 			}
749 737
 
750 738
 			if 0 < len(oldModes) {
751
-				session.Send(nil, channel.server.name, "MODE", channel.name, oldModes, nick)
739
+				session.Send(nil, channel.server.name, "MODE", chname, oldModes, details.nick)
752 740
 			}
753 741
 		}
754 742
 	}
755 743
 
756
-	rb := NewResponseBuffer(newClient.Sessions()[0])
744
+	rb := NewResponseBuffer(session)
757 745
 	// use blocking i/o to synchronize with the later history replay
758 746
 	if rb.session.capabilities.Has(caps.ExtendedJoin) {
759
-		rb.Add(nil, nickMask, "JOIN", channel.name, accountName, realName)
747
+		rb.Add(nil, details.nickMask, "JOIN", channel.name, details.accountName, realName)
760 748
 	} else {
761
-		rb.Add(nil, nickMask, "JOIN", channel.name)
762
-	}
763
-	channel.SendTopic(newClient, rb, false)
764
-	channel.Names(newClient, rb)
765
-	if 0 < len(oldModes) {
766
-		rb.Add(nil, newClient.server.name, "MODE", channel.name, oldModes, nick)
749
+		rb.Add(nil, details.nickMask, "JOIN", channel.name)
767 750
 	}
751
+	channel.SendTopic(session.client, rb, false)
752
+	channel.Names(session.client, rb)
768 753
 	rb.Send(true)
769 754
 }
770 755
 
771
-func (channel *Channel) replayHistoryForResume(newClient *Client, after time.Time, before time.Time) {
756
+func (channel *Channel) replayHistoryForResume(session *Session, after time.Time, before time.Time) {
772 757
 	items, complete := channel.history.Between(after, before, false, 0)
773
-	rb := NewResponseBuffer(newClient.Sessions()[0])
758
+	rb := NewResponseBuffer(session)
774 759
 	channel.replayHistoryItems(rb, items, false)
775
-	if !complete && !newClient.resumeDetails.HistoryIncomplete {
760
+	if !complete && !session.resumeDetails.HistoryIncomplete {
776 761
 		// warn here if we didn't warn already
777
-		rb.Add(nil, "HistServ", "NOTICE", channel.Name(), newClient.t("Some additional message history may have been lost"))
762
+		rb.Add(nil, histServMask, "NOTICE", channel.Name(), session.client.t("Some additional message history may have been lost"))
778 763
 	}
779 764
 	rb.Send(true)
780 765
 }
@@ -828,7 +813,7 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
828 813
 				} else {
829 814
 					message = fmt.Sprintf(client.t("%[1]s [account: %[2]s] joined the channel"), nick, item.AccountName)
830 815
 				}
831
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), "HistServ", "*", nil, "PRIVMSG", chname, message)
816
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message)
832 817
 			}
833 818
 		case history.Part:
834 819
 			if eventPlayback {
@@ -838,14 +823,14 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
838 823
 					continue // #474
839 824
 				}
840 825
 				message := fmt.Sprintf(client.t("%[1]s left the channel (%[2]s)"), nick, item.Message.Message)
841
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), "HistServ", "*", nil, "PRIVMSG", chname, message)
826
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message)
842 827
 			}
843 828
 		case history.Kick:
844 829
 			if eventPlayback {
845 830
 				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "HEVENT", "KICK", chname, item.Params[0], item.Message.Message)
846 831
 			} else {
847 832
 				message := fmt.Sprintf(client.t("%[1]s kicked %[2]s (%[3]s)"), nick, item.Params[0], item.Message.Message)
848
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), "HistServ", "*", nil, "PRIVMSG", chname, message)
833
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message)
849 834
 			}
850 835
 		case history.Quit:
851 836
 			if eventPlayback {
@@ -855,14 +840,14 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
855 840
 					continue // #474
856 841
 				}
857 842
 				message := fmt.Sprintf(client.t("%[1]s quit (%[2]s)"), nick, item.Message.Message)
858
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), "HistServ", "*", nil, "PRIVMSG", chname, message)
843
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message)
859 844
 			}
860 845
 		case history.Nick:
861 846
 			if eventPlayback {
862 847
 				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "HEVENT", "NICK", item.Params[0])
863 848
 			} else {
864 849
 				message := fmt.Sprintf(client.t("%[1]s changed nick to %[2]s"), nick, item.Params[0])
865
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), "HistServ", "*", nil, "PRIVMSG", chname, message)
850
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message)
866 851
 			}
867 852
 		}
868 853
 	}

+ 107
- 161
irc/client.go Zobrazit soubor

@@ -36,11 +36,8 @@ const (
36 36
 // the resume process: when handling the RESUME command itself,
37 37
 // when completing the registration, and when rejoining channels.
38 38
 type ResumeDetails struct {
39
-	OldClient         *Client
40 39
 	PresentedToken    string
41 40
 	Timestamp         time.Time
42
-	ResumedAt         time.Time
43
-	Channels          []string
44 41
 	HistoryIncomplete bool
45 42
 }
46 43
 
@@ -52,6 +49,7 @@ type Client struct {
52 49
 	atime              time.Time
53 50
 	away               bool
54 51
 	awayMessage        string
52
+	brbTimer           BrbTimer
55 53
 	certfp             string
56 54
 	channels           ChannelSet
57 55
 	ctime              time.Time
@@ -75,7 +73,6 @@ type Client struct {
75 73
 	realname           string
76 74
 	realIP             net.IP
77 75
 	registered         bool
78
-	resumeDetails      *ResumeDetails
79 76
 	resumeID           string
80 77
 	saslInProgress     bool
81 78
 	saslMechanism      string
@@ -87,7 +84,7 @@ type Client struct {
87 84
 	stateMutex         sync.RWMutex // tier 1
88 85
 	username           string
89 86
 	vhost              string
90
-	history            *history.Buffer
87
+	history            history.Buffer
91 88
 }
92 89
 
93 90
 // Session is an individual client connection to the server (TCP connection
@@ -113,6 +110,9 @@ type Session struct {
113 110
 	maxlenRest   uint32
114 111
 	capState     caps.State
115 112
 	capVersion   caps.Version
113
+
114
+	resumeID      string
115
+	resumeDetails *ResumeDetails
116 116
 }
117 117
 
118 118
 // sets the session quit message, if there isn't one already
@@ -207,8 +207,9 @@ func (server *Server) RunClient(conn clientConn) {
207 207
 		nick:           "*", // * is used until actual nick is given
208 208
 		nickCasefolded: "*",
209 209
 		nickMaskString: "*", // * is used until actual nick is given
210
-		history:        history.NewHistoryBuffer(config.History.ClientLength),
211 210
 	}
211
+	client.history.Initialize(config.History.ClientLength)
212
+	client.brbTimer.Initialize(client)
212 213
 	session := &Session{
213 214
 		client:     client,
214 215
 		socket:     socket,
@@ -339,7 +340,7 @@ func (client *Client) run(session *Session) {
339 340
 			}
340 341
 		}
341 342
 		// ensure client connection gets closed
342
-		client.destroy(false, session)
343
+		client.destroy(session)
343 344
 	}()
344 345
 
345 346
 	session.idletimer.Initialize(session)
@@ -347,7 +348,13 @@ func (client *Client) run(session *Session) {
347 348
 
348 349
 	isReattach := client.Registered()
349 350
 	if isReattach {
350
-		client.playReattachMessages(session)
351
+		if session.resumeDetails != nil {
352
+			session.playResume()
353
+			session.resumeDetails = nil
354
+			client.brbTimer.Disable()
355
+		} else {
356
+			client.playReattachMessages(session)
357
+		}
351 358
 	} else {
352 359
 		// don't reset the nick timer during a reattach
353 360
 		client.nickTimer.Initialize(client)
@@ -365,6 +372,9 @@ func (client *Client) run(session *Session) {
365 372
 				quitMessage = "readQ exceeded"
366 373
 			}
367 374
 			client.Quit(quitMessage, session)
375
+			// since the client did not actually send us a QUIT,
376
+			// give them a chance to resume or reattach if applicable:
377
+			client.brbTimer.Enable()
368 378
 			break
369 379
 		}
370 380
 
@@ -443,83 +453,66 @@ func (session *Session) Ping() {
443 453
 }
444 454
 
445 455
 // tryResume tries to resume if the client asked us to.
446
-func (client *Client) tryResume() (success bool) {
447
-	server := client.server
448
-	config := server.Config()
456
+func (session *Session) tryResume() (success bool) {
457
+	var oldResumeID string
449 458
 
450 459
 	defer func() {
451
-		if !success {
452
-			client.resumeDetails = nil
460
+		if success {
461
+			// "On a successful request, the server [...] terminates the old client's connection"
462
+			oldSession := session.client.GetSessionByResumeID(oldResumeID)
463
+			if oldSession != nil {
464
+				session.client.destroy(oldSession)
465
+			}
466
+		} else {
467
+			session.resumeDetails = nil
453 468
 		}
454 469
 	}()
455 470
 
456
-	timestamp := client.resumeDetails.Timestamp
457
-	var timestampString string
458
-	if !timestamp.IsZero() {
459
-		timestampString = timestamp.UTC().Format(IRCv3TimestampFormat)
460
-	}
471
+	client := session.client
472
+	server := client.server
473
+	config := server.Config()
461 474
 
462
-	oldClient := server.resumeManager.VerifyToken(client.resumeDetails.PresentedToken)
475
+	oldClient, oldResumeID := server.resumeManager.VerifyToken(client, session.resumeDetails.PresentedToken)
463 476
 	if oldClient == nil {
464
-		client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection, token is not valid"))
477
+		session.Send(nil, server.name, "FAIL", "RESUME", "INVALID_TOKEN", client.t("Cannot resume connection, token is not valid"))
465 478
 		return
466 479
 	}
467
-	oldNick := oldClient.Nick()
468
-	oldNickmask := oldClient.NickMaskString()
469 480
 
470 481
 	resumeAllowed := config.Server.AllowPlaintextResume || (oldClient.HasMode(modes.TLS) && client.HasMode(modes.TLS))
471 482
 	if !resumeAllowed {
472
-		client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection, old and new clients must have TLS"))
483
+		session.Send(nil, server.name, "FAIL", "RESUME", "INSECURE_SESSION", client.t("Cannot resume connection, old and new clients must have TLS"))
473 484
 		return
474 485
 	}
475 486
 
476 487
 	if oldClient.isTor != client.isTor {
477
-		client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection from Tor to non-Tor or vice versa"))
478
-		return
479
-	}
480
-
481
-	if 1 < len(oldClient.Sessions()) {
482
-		client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume a client with multiple attached sessions"))
488
+		session.Send(nil, server.name, "FAIL", "RESUME", "INSECURE_SESSION", client.t("Cannot resume connection from Tor to non-Tor or vice versa"))
483 489
 		return
484 490
 	}
485 491
 
486
-	err := server.clients.Resume(client, oldClient)
492
+	err := server.clients.Resume(oldClient, session)
487 493
 	if err != nil {
488
-		client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection"))
494
+		session.Send(nil, server.name, "FAIL", "RESUME", "CANNOT_RESUME", client.t("Cannot resume connection"))
489 495
 		return
490 496
 	}
491 497
 
492 498
 	success = true
499
+	client.server.logger.Debug("quit", fmt.Sprintf("%s is being resumed", oldClient.Nick()))
493 500
 
494
-	// this is a bit racey
495
-	client.resumeDetails.ResumedAt = time.Now().UTC()
496
-
497
-	client.nickTimer.Touch(nil)
498
-
499
-	// resume successful, proceed to copy client state (nickname, flags, etc.)
500
-	// after this, the server thinks that `newClient` owns the nickname
501
-
502
-	client.resumeDetails.OldClient = oldClient
503
-
504
-	// transfer monitor stuff
505
-	server.monitorManager.Resume(client, oldClient)
506
-
507
-	// record the names, not the pointers, of the channels,
508
-	// to avoid dumb annoying race conditions
509
-	channels := oldClient.Channels()
510
-	client.resumeDetails.Channels = make([]string, len(channels))
511
-	for i, channel := range channels {
512
-		client.resumeDetails.Channels[i] = channel.Name()
513
-	}
501
+	return
502
+}
514 503
 
515
-	username := client.Username()
516
-	hostname := client.Hostname()
504
+// playResume is called from the session's fresh goroutine after a resume;
505
+// it sends notifications to friends, then plays the registration burst and replays
506
+// stored history to the session
507
+func (session *Session) playResume() {
508
+	client := session.client
509
+	server := client.server
517 510
 
518 511
 	friends := make(ClientSet)
519 512
 	oldestLostMessage := time.Now().UTC()
520 513
 
521 514
 	// work out how much time, if any, is not covered by history buffers
522
-	for _, channel := range channels {
515
+	for _, channel := range client.Channels() {
523 516
 		for _, member := range channel.Members() {
524 517
 			friends.Add(member)
525 518
 			lastDiscarded := channel.history.LastDiscarded()
@@ -531,8 +524,8 @@ func (client *Client) tryResume() (success bool) {
531 524
 	privmsgMatcher := func(item history.Item) bool {
532 525
 		return item.Type == history.Privmsg || item.Type == history.Notice || item.Type == history.Tagmsg
533 526
 	}
534
-	privmsgHistory := oldClient.history.Match(privmsgMatcher, false, 0)
535
-	lastDiscarded := oldClient.history.LastDiscarded()
527
+	privmsgHistory := client.history.Match(privmsgMatcher, false, 0)
528
+	lastDiscarded := client.history.LastDiscarded()
536 529
 	if lastDiscarded.Before(oldestLostMessage) {
537 530
 		oldestLostMessage = lastDiscarded
538 531
 	}
@@ -543,60 +536,61 @@ func (client *Client) tryResume() (success bool) {
543 536
 		}
544 537
 	}
545 538
 
539
+	timestamp := session.resumeDetails.Timestamp
546 540
 	gap := lastDiscarded.Sub(timestamp)
547
-	client.resumeDetails.HistoryIncomplete = gap > 0
541
+	session.resumeDetails.HistoryIncomplete = gap > 0
548 542
 	gapSeconds := int(gap.Seconds()) + 1 // round up to avoid confusion
549 543
 
544
+	details := client.Details()
545
+	oldNickmask := details.nickMask
546
+	client.SetRawHostname(session.rawHostname)
547
+	hostname := client.Hostname() // may be a vhost
548
+	timestampString := session.resumeDetails.Timestamp.Format(IRCv3TimestampFormat)
549
+
550 550
 	// send quit/resume messages to friends
551 551
 	for friend := range friends {
552
-		for _, session := range friend.Sessions() {
553
-			if session.capabilities.Has(caps.Resume) {
552
+		if friend == client {
553
+			continue
554
+		}
555
+		for _, fSession := range friend.Sessions() {
556
+			if fSession.capabilities.Has(caps.Resume) {
554 557
 				if timestamp.IsZero() {
555
-					session.Send(nil, oldNickmask, "RESUMED", username, hostname)
558
+					fSession.Send(nil, oldNickmask, "RESUMED", hostname)
556 559
 				} else {
557
-					session.Send(nil, oldNickmask, "RESUMED", username, hostname, timestampString)
560
+					fSession.Send(nil, oldNickmask, "RESUMED", hostname, timestampString)
558 561
 				}
559 562
 			} else {
560
-				if client.resumeDetails.HistoryIncomplete {
561
-					session.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected (up to %d seconds of history lost)"), gapSeconds))
563
+				if session.resumeDetails.HistoryIncomplete {
564
+					fSession.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected (up to %d seconds of history lost)"), gapSeconds))
562 565
 				} else {
563
-					session.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected")))
566
+					fSession.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected")))
564 567
 				}
565 568
 			}
566 569
 		}
567 570
 	}
568 571
 
569
-	if client.resumeDetails.HistoryIncomplete {
570
-		client.Send(nil, client.server.name, "RESUME", "WARN", fmt.Sprintf(client.t("Resume may have lost up to %d seconds of history"), gapSeconds))
572
+	if session.resumeDetails.HistoryIncomplete {
573
+		session.Send(nil, client.server.name, "RESUME", "WARN", "HISTORY_LOST", fmt.Sprintf(client.t("Resume may have lost up to %d seconds of history"), gapSeconds))
571 574
 	}
572 575
 
573
-	client.Send(nil, client.server.name, "RESUME", "SUCCESS", oldNick)
574
-
575
-	// after we send the rest of the registration burst, we'll try rejoining channels
576
-	return
577
-}
576
+	session.Send(nil, client.server.name, "RESUME", details.nick)
578 577
 
579
-func (client *Client) tryResumeChannels() {
580
-	details := client.resumeDetails
578
+	server.playRegistrationBurst(session)
581 579
 
582
-	for _, name := range details.Channels {
583
-		channel := client.server.channels.Get(name)
584
-		if channel == nil {
585
-			continue
586
-		}
587
-		channel.Resume(client, details.OldClient, details.Timestamp)
580
+	for _, channel := range client.Channels() {
581
+		channel.Resume(session, timestamp)
588 582
 	}
589 583
 
590 584
 	// replay direct PRIVSMG history
591
-	if !details.Timestamp.IsZero() {
585
+	if !timestamp.IsZero() {
592 586
 		now := time.Now().UTC()
593
-		items, complete := client.history.Between(details.Timestamp, now, false, 0)
587
+		items, complete := client.history.Between(timestamp, now, false, 0)
594 588
 		rb := NewResponseBuffer(client.Sessions()[0])
595 589
 		client.replayPrivmsgHistory(rb, items, complete)
596 590
 		rb.Send(true)
597 591
 	}
598 592
 
599
-	details.OldClient.destroy(true, nil)
593
+	session.resumeDetails = nil
600 594
 }
601 595
 
602 596
 func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.Item, complete bool) {
@@ -644,41 +638,6 @@ func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.I
644 638
 	}
645 639
 }
646 640
 
647
-// copy applicable state from oldClient to client as part of a resume
648
-func (client *Client) copyResumeData(oldClient *Client) {
649
-	oldClient.stateMutex.RLock()
650
-	history := oldClient.history
651
-	nick := oldClient.nick
652
-	nickCasefolded := oldClient.nickCasefolded
653
-	vhost := oldClient.vhost
654
-	account := oldClient.account
655
-	accountName := oldClient.accountName
656
-	skeleton := oldClient.skeleton
657
-	oldClient.stateMutex.RUnlock()
658
-
659
-	// copy all flags, *except* TLS (in the case that the admins enabled
660
-	// resume over plaintext)
661
-	hasTLS := client.flags.HasMode(modes.TLS)
662
-	temp := modes.NewModeSet()
663
-	temp.Copy(&oldClient.flags)
664
-	temp.SetMode(modes.TLS, hasTLS)
665
-	client.flags.Copy(temp)
666
-
667
-	client.stateMutex.Lock()
668
-	defer client.stateMutex.Unlock()
669
-
670
-	// reuse the old client's history buffer
671
-	client.history = history
672
-	// copy other data
673
-	client.nick = nick
674
-	client.nickCasefolded = nickCasefolded
675
-	client.vhost = vhost
676
-	client.account = account
677
-	client.accountName = accountName
678
-	client.skeleton = skeleton
679
-	client.updateNickMaskNoMutex()
680
-}
681
-
682 641
 // IdleTime returns how long this client's been idle.
683 642
 func (client *Client) IdleTime() time.Duration {
684 643
 	client.stateMutex.RLock()
@@ -956,12 +915,13 @@ func (client *Client) Quit(message string, session *Session) {
956 915
 // if `session` is nil, destroys the client unconditionally, removing all sessions;
957 916
 // otherwise, destroys one specific session, only destroying the client if it
958 917
 // has no more sessions.
959
-func (client *Client) destroy(beingResumed bool, session *Session) {
918
+func (client *Client) destroy(session *Session) {
960 919
 	var sessionsToDestroy []*Session
961 920
 
962 921
 	// allow destroy() to execute at most once
963 922
 	client.stateMutex.Lock()
964 923
 	details := client.detailsNoMutex()
924
+	brbState := client.brbTimer.state
965 925
 	wasReattach := session != nil && session.client != client
966 926
 	sessionRemoved := false
967 927
 	var remainingSessions int
@@ -977,10 +937,6 @@ func (client *Client) destroy(beingResumed bool, session *Session) {
977 937
 	}
978 938
 	client.stateMutex.Unlock()
979 939
 
980
-	if len(sessionsToDestroy) == 0 {
981
-		return
982
-	}
983
-
984 940
 	// destroy all applicable sessions:
985 941
 	var quitMessage string
986 942
 	for _, session := range sessionsToDestroy {
@@ -1010,8 +966,8 @@ func (client *Client) destroy(beingResumed bool, session *Session) {
1010 966
 		client.server.logger.Info("localconnect-ip", fmt.Sprintf("disconnecting session of %s from %s", details.nick, source))
1011 967
 	}
1012 968
 
1013
-	// ok, now destroy the client, unless it still has sessions:
1014
-	if remainingSessions != 0 {
969
+	// do not destroy the client if it has either remaining sessions, or is BRB'ed
970
+	if remainingSessions != 0 || brbState == BrbEnabled || brbState == BrbSticky {
1015 971
 		return
1016 972
 	}
1017 973
 
@@ -1020,14 +976,12 @@ func (client *Client) destroy(beingResumed bool, session *Session) {
1020 976
 	client.server.semaphores.ClientDestroy.Acquire()
1021 977
 	defer client.server.semaphores.ClientDestroy.Release()
1022 978
 
1023
-	if beingResumed {
1024
-		client.server.logger.Debug("quit", fmt.Sprintf("%s is being resumed", details.nick))
1025
-	} else if !wasReattach {
979
+	if !wasReattach {
1026 980
 		client.server.logger.Debug("quit", fmt.Sprintf("%s is no longer on the server", details.nick))
1027 981
 	}
1028 982
 
1029 983
 	registered := client.Registered()
1030
-	if !beingResumed && registered {
984
+	if registered {
1031 985
 		client.server.whoWas.Append(client.WhoWas())
1032 986
 	}
1033 987
 
@@ -1045,15 +999,13 @@ func (client *Client) destroy(beingResumed bool, session *Session) {
1045 999
 	// (note that if this is a reattach, client has no channels and therefore no friends)
1046 1000
 	friends := make(ClientSet)
1047 1001
 	for _, channel := range client.Channels() {
1048
-		if !beingResumed {
1049
-			channel.Quit(client)
1050
-			channel.history.Add(history.Item{
1051
-				Type:        history.Quit,
1052
-				Nick:        details.nickMask,
1053
-				AccountName: details.accountName,
1054
-				Message:     splitQuitMessage,
1055
-			})
1056
-		}
1002
+		channel.Quit(client)
1003
+		channel.history.Add(history.Item{
1004
+			Type:        history.Quit,
1005
+			Nick:        details.nickMask,
1006
+			AccountName: details.accountName,
1007
+			Message:     splitQuitMessage,
1008
+		})
1057 1009
 		for _, member := range channel.Members() {
1058 1010
 			friends.Add(member)
1059 1011
 		}
@@ -1061,40 +1013,34 @@ func (client *Client) destroy(beingResumed bool, session *Session) {
1061 1013
 	friends.Remove(client)
1062 1014
 
1063 1015
 	// clean up server
1064
-	if !beingResumed {
1065
-		client.server.clients.Remove(client)
1066
-	}
1016
+	client.server.clients.Remove(client)
1067 1017
 
1068 1018
 	// clean up self
1069 1019
 	client.nickTimer.Stop()
1020
+	client.brbTimer.Disable()
1070 1021
 
1071 1022
 	client.server.accounts.Logout(client)
1072 1023
 
1073 1024
 	// send quit messages to friends
1074
-	if !beingResumed {
1075
-		if registered {
1076
-			client.server.stats.ChangeTotal(-1)
1077
-		}
1078
-		if client.HasMode(modes.Invisible) {
1079
-			client.server.stats.ChangeInvisible(-1)
1080
-		}
1081
-		if client.HasMode(modes.Operator) || client.HasMode(modes.LocalOperator) {
1082
-			client.server.stats.ChangeOperators(-1)
1083
-		}
1025
+	if registered {
1026
+		client.server.stats.ChangeTotal(-1)
1027
+	}
1028
+	if client.HasMode(modes.Invisible) {
1029
+		client.server.stats.ChangeInvisible(-1)
1030
+	}
1031
+	if client.HasMode(modes.Operator) || client.HasMode(modes.LocalOperator) {
1032
+		client.server.stats.ChangeOperators(-1)
1033
+	}
1084 1034
 
1085
-		for friend := range friends {
1086
-			if quitMessage == "" {
1087
-				quitMessage = "Exited"
1088
-			}
1089
-			friend.sendFromClientInternal(false, splitQuitMessage.Time, splitQuitMessage.Msgid, details.nickMask, details.accountName, nil, "QUIT", quitMessage)
1035
+	for friend := range friends {
1036
+		if quitMessage == "" {
1037
+			quitMessage = "Exited"
1090 1038
 		}
1039
+		friend.sendFromClientInternal(false, splitQuitMessage.Time, splitQuitMessage.Msgid, details.nickMask, details.accountName, nil, "QUIT", quitMessage)
1091 1040
 	}
1092
-	if !client.exitedSnomaskSent {
1093
-		if beingResumed {
1094
-			client.server.snomasks.Send(sno.LocalQuits, fmt.Sprintf(ircfmt.Unescape("%s$r is resuming their connection, old client has been destroyed"), client.nick))
1095
-		} else if registered {
1096
-			client.server.snomasks.Send(sno.LocalQuits, fmt.Sprintf(ircfmt.Unescape("%s$r exited the network"), details.nick))
1097
-		}
1041
+
1042
+	if !client.exitedSnomaskSent && registered {
1043
+		client.server.snomasks.Send(sno.LocalQuits, fmt.Sprintf(ircfmt.Unescape("%s$r exited the network"), details.nick))
1098 1044
 	}
1099 1045
 }
1100 1046
 

+ 10
- 36
irc/client_lookup_set.go Zobrazit soubor

@@ -108,26 +108,21 @@ func (clients *ClientManager) Remove(client *Client) error {
108 108
 	return clients.removeInternal(client)
109 109
 }
110 110
 
111
-// Resume atomically replaces `oldClient` with `newClient`, updating
112
-// newClient's data to match. It is the caller's responsibility first
113
-// to verify that the resume is allowed, and then later to call oldClient.destroy().
114
-func (clients *ClientManager) Resume(newClient, oldClient *Client) (err error) {
111
+// Handles a RESUME by attaching a session to a designated client. It is the
112
+// caller's responsibility to verify that the resume is allowed (checking tokens,
113
+// TLS status, etc.) before calling this.
114
+func (clients *ClientManager) Resume(oldClient *Client, session *Session) (err error) {
115 115
 	clients.Lock()
116 116
 	defer clients.Unlock()
117 117
 
118
-	// atomically grant the new client the old nick
119
-	err = clients.removeInternal(oldClient)
120
-	if err != nil {
121
-		// oldClient no longer owns its nick, fail out
122
-		return err
118
+	cfnick := oldClient.NickCasefolded()
119
+	if _, ok := clients.byNick[cfnick]; !ok {
120
+		return errNickMissing
123 121
 	}
124
-	// nick has been reclaimed, grant it to the new client
125
-	clients.removeInternal(newClient)
126
-	oldcfnick, oldskeleton := oldClient.uniqueIdentifiers()
127
-	clients.byNick[oldcfnick] = newClient
128
-	clients.bySkeleton[oldskeleton] = newClient
129 122
 
130
-	newClient.copyResumeData(oldClient)
123
+	if !oldClient.AddSession(session) {
124
+		return errNickMissing
125
+	}
131 126
 
132 127
 	return nil
133 128
 }
@@ -256,27 +251,6 @@ func (clients *ClientManager) FindAll(userhost string) (set ClientSet) {
256 251
 	return set
257 252
 }
258 253
 
259
-// Find returns the first client that matches the given userhost mask.
260
-func (clients *ClientManager) Find(userhost string) *Client {
261
-	userhost, err := Casefold(ExpandUserHost(userhost))
262
-	if err != nil {
263
-		return nil
264
-	}
265
-	matcher := ircmatch.MakeMatch(userhost)
266
-	var matchedClient *Client
267
-
268
-	clients.RLock()
269
-	defer clients.RUnlock()
270
-	for _, client := range clients.byNick {
271
-		if matcher.Match(client.NickMaskCasefolded()) {
272
-			matchedClient = client
273
-			break
274
-		}
275
-	}
276
-
277
-	return matchedClient
278
-}
279
-
280 254
 //
281 255
 // usermask to regexp
282 256
 //

+ 4
- 0
irc/commands.go Zobrazit soubor

@@ -88,6 +88,10 @@ func init() {
88 88
 			handler:   awayHandler,
89 89
 			minParams: 0,
90 90
 		},
91
+		"BRB": {
92
+			handler:   brbHandler,
93
+			minParams: 0,
94
+		},
91 95
 		"CAP": {
92 96
 			handler:      capHandler,
93 97
 			usablePreReg: true,

+ 35
- 1
irc/getters.go Zobrazit soubor

@@ -65,6 +65,18 @@ func (client *Client) Sessions() (sessions []*Session) {
65 65
 	return
66 66
 }
67 67
 
68
+func (client *Client) GetSessionByResumeID(resumeID string) (result *Session) {
69
+	client.stateMutex.RLock()
70
+	defer client.stateMutex.RUnlock()
71
+
72
+	for _, session := range client.sessions {
73
+		if session.resumeID == resumeID {
74
+			return session
75
+		}
76
+	}
77
+	return
78
+}
79
+
68 80
 type SessionData struct {
69 81
 	ctime    time.Time
70 82
 	atime    time.Time
@@ -100,9 +112,17 @@ func (client *Client) AddSession(session *Session) (success bool) {
100 112
 	client.stateMutex.Lock()
101 113
 	defer client.stateMutex.Unlock()
102 114
 
103
-	if len(client.sessions) == 0 {
115
+	// client may be dying and ineligible to receive another session
116
+	switch client.brbTimer.state {
117
+	case BrbDisabled:
118
+		if len(client.sessions) == 0 {
119
+			return false
120
+		}
121
+	case BrbDead:
104 122
 		return false
123
+		// default: BrbEnabled or BrbSticky, proceed
105 124
 	}
125
+	// success, attach the new session to the client
106 126
 	session.client = client
107 127
 	client.sessions = append(client.sessions, session)
108 128
 	return true
@@ -125,6 +145,12 @@ func (client *Client) removeSession(session *Session) (success bool, length int)
125 145
 	return
126 146
 }
127 147
 
148
+func (session *Session) SetResumeID(resumeID string) {
149
+	session.client.stateMutex.Lock()
150
+	session.resumeID = resumeID
151
+	session.client.stateMutex.Unlock()
152
+}
153
+
128 154
 func (client *Client) Nick() string {
129 155
 	client.stateMutex.RLock()
130 156
 	defer client.stateMutex.RUnlock()
@@ -233,6 +259,14 @@ func (client *Client) RawHostname() (result string) {
233 259
 	return
234 260
 }
235 261
 
262
+func (client *Client) SetRawHostname(rawHostname string) {
263
+	client.stateMutex.Lock()
264
+	defer client.stateMutex.Unlock()
265
+
266
+	client.rawHostname = rawHostname
267
+	client.updateNickMaskNoMutex()
268
+}
269
+
236 270
 func (client *Client) AwayMessage() (result string) {
237 271
 	client.stateMutex.RLock()
238 272
 	result = client.awayMessage

+ 40
- 17
irc/handlers.go Zobrazit soubor

@@ -502,6 +502,31 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
502 502
 	return false
503 503
 }
504 504
 
505
+// BRB [message]
506
+func brbHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
507
+	success, duration := client.brbTimer.Enable()
508
+	if !success {
509
+		rb.Add(nil, server.name, "FAIL", "BRB", "CANNOT_BRB", client.t("Your client does not support BRB"))
510
+		return false
511
+	} else {
512
+		rb.Add(nil, server.name, "BRB", strconv.Itoa(int(duration.Seconds())))
513
+	}
514
+
515
+	var message string
516
+	if 0 < len(msg.Params) {
517
+		message = msg.Params[0]
518
+	} else {
519
+		message = client.t("I'll be right back")
520
+	}
521
+
522
+	if len(client.Sessions()) == 1 {
523
+		// true BRB
524
+		client.SetAway(true, message)
525
+	}
526
+
527
+	return true
528
+}
529
+
505 530
 // CAP <subcmd> [<caps>]
506 531
 func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
507 532
 	subCommand := strings.ToUpper(msg.Params[0])
@@ -568,9 +593,10 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
568 593
 		// if this is the first time the client is requesting a resume token,
569 594
 		// send it to them
570 595
 		if toAdd.Has(caps.Resume) {
571
-			token := server.resumeManager.GenerateToken(client)
596
+			token, id := server.resumeManager.GenerateToken(client)
572 597
 			if token != "" {
573 598
 				rb.Add(nil, server.name, "RESUME", "TOKEN", token)
599
+				rb.session.SetResumeID(id)
574 600
 			}
575 601
 		}
576 602
 
@@ -638,7 +664,7 @@ func chathistoryHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r
638 664
 			myAccount := client.Account()
639 665
 			targetAccount := targetClient.Account()
640 666
 			if myAccount != "" && targetAccount != "" && myAccount == targetAccount {
641
-				hist = targetClient.history
667
+				hist = &targetClient.history
642 668
 			}
643 669
 		}
644 670
 	}
@@ -1024,7 +1050,7 @@ func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
1024 1050
 				killClient = true
1025 1051
 			} else {
1026 1052
 				// if mcl == client, we kill them below
1027
-				mcl.destroy(false, nil)
1053
+				mcl.destroy(nil)
1028 1054
 			}
1029 1055
 		}
1030 1056
 
@@ -1087,13 +1113,13 @@ func historyHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
1087 1113
 		hist = &channel.history
1088 1114
 	} else {
1089 1115
 		if strings.ToLower(target) == "me" {
1090
-			hist = client.history
1116
+			hist = &client.history
1091 1117
 		} else {
1092 1118
 			targetClient := server.clients.Get(target)
1093 1119
 			if targetClient != nil {
1094 1120
 				myAccount, targetAccount := client.Account(), targetClient.Account()
1095 1121
 				if myAccount != "" && targetAccount != "" && myAccount == targetAccount {
1096
-					hist = targetClient.history
1122
+					hist = &targetClient.history
1097 1123
 				}
1098 1124
 			}
1099 1125
 		}
@@ -1331,7 +1357,7 @@ func killHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
1331 1357
 	target.exitedSnomaskSent = true
1332 1358
 
1333 1359
 	target.Quit(quitMsg, nil)
1334
-	target.destroy(false, nil)
1360
+	target.destroy(nil)
1335 1361
 	return false
1336 1362
 }
1337 1363
 
@@ -1461,7 +1487,7 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
1461 1487
 				killClient = true
1462 1488
 			} else {
1463 1489
 				// if mcl == client, we kill them below
1464
-				mcl.destroy(false, nil)
1490
+				mcl.destroy(nil)
1465 1491
 			}
1466 1492
 		}
1467 1493
 
@@ -2326,28 +2352,25 @@ func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
2326 2352
 
2327 2353
 // RESUME <token> [timestamp]
2328 2354
 func resumeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
2329
-	token := msg.Params[0]
2355
+	details := ResumeDetails{
2356
+		PresentedToken: msg.Params[0],
2357
+	}
2330 2358
 
2331 2359
 	if client.registered {
2332
-		rb.Add(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection, connection registration has already been completed"))
2360
+		rb.Add(nil, server.name, "FAIL", "RESUME", "REGISTRATION_IS_COMPLETED", client.t("Cannot resume connection, connection registration has already been completed"))
2333 2361
 		return false
2334 2362
 	}
2335 2363
 
2336
-	var timestamp time.Time
2337 2364
 	if 1 < len(msg.Params) {
2338 2365
 		ts, err := time.Parse(IRCv3TimestampFormat, msg.Params[1])
2339 2366
 		if err == nil {
2340
-			timestamp = ts
2367
+			details.Timestamp = ts
2341 2368
 		} else {
2342
-			rb.Add(nil, server.name, "RESUME", "WARN", client.t("Timestamp is not in 2006-01-02T15:04:05.999Z format, ignoring it"))
2369
+			rb.Add(nil, server.name, "WARN", "RESUME", "HISTORY_LOST", client.t("Timestamp is not in 2006-01-02T15:04:05.999Z format, ignoring it"))
2343 2370
 		}
2344 2371
 	}
2345 2372
 
2346
-	client.resumeDetails = &ResumeDetails{
2347
-		Timestamp:      timestamp,
2348
-		PresentedToken: token,
2349
-	}
2350
-
2373
+	rb.session.resumeDetails = &details
2351 2374
 	return false
2352 2375
 }
2353 2376
 

+ 8
- 0
irc/help.go Zobrazit soubor

@@ -120,6 +120,14 @@ http://ircv3.net/specs/extensions/sasl-3.1.html`,
120 120
 
121 121
 If [message] is sent, marks you away. If [message] is not sent, marks you no
122 122
 longer away.`,
123
+	},
124
+	"brb": {
125
+		text: `BRB [message]
126
+
127
+Disconnects you from the server, while instructing the server to keep you
128
+present for a short time window. During this window, you can either resume
129
+or reattach to your nickname. If [message] is sent, it is used as your away
130
+message (and as your quit message if you don't return in time).`,
123 131
 	},
124 132
 	"cap": {
125 133
 		text: `CAP <subcommand> [:<capabilities>]

+ 137
- 2
irc/idletimer.go Zobrazit soubor

@@ -126,7 +126,7 @@ func (it *IdleTimer) processTimeout() {
126 126
 		it.session.Ping()
127 127
 	} else {
128 128
 		it.session.client.Quit(it.quitMessage(previousState), it.session)
129
-		it.session.client.destroy(false, it.session)
129
+		it.session.client.destroy(it.session)
130 130
 	}
131 131
 }
132 132
 
@@ -157,7 +157,11 @@ func (it *IdleTimer) resetTimeout() {
157 157
 	case TimerDead:
158 158
 		return
159 159
 	}
160
-	it.timer = time.AfterFunc(nextTimeout, it.processTimeout)
160
+	if it.timer != nil {
161
+		it.timer.Reset(nextTimeout)
162
+	} else {
163
+		it.timer = time.AfterFunc(nextTimeout, it.processTimeout)
164
+	}
161 165
 }
162 166
 
163 167
 func (it *IdleTimer) quitMessage(state TimerState) string {
@@ -300,3 +304,134 @@ func (nt *NickTimer) processTimeout() {
300 304
 	nt.client.Notice(fmt.Sprintf(nt.client.t(baseMsg), nt.Timeout()))
301 305
 	nt.client.server.RandomlyRename(nt.client)
302 306
 }
307
+
308
+// BrbTimer is a timer on the client as a whole (not an individual session) for implementing
309
+// the BRB command and related functionality (where a client can remain online without
310
+// having any connected sessions).
311
+
312
+type BrbState uint
313
+
314
+const (
315
+	// BrbDisabled is the default state; the client will be disconnected if it has no sessions
316
+	BrbDisabled BrbState = iota
317
+	// BrbEnabled allows the client to remain online without sessions; if a timeout is
318
+	// reached, it will be removed
319
+	BrbEnabled
320
+	// BrbDead is the state of a client after its timeout has expired; it will be removed
321
+	// and therefore new sessions cannot be attached to it
322
+	BrbDead
323
+	// BrbSticky allows a client to remain online without sessions, with no timeout.
324
+	// This is not used yet.
325
+	BrbSticky
326
+)
327
+
328
+type BrbTimer struct {
329
+	// XXX we use client.stateMutex for synchronization, so we can atomically test
330
+	// conditions that use both brbTimer.state and client.sessions. This code
331
+	// is tightly coupled with the rest of Client.
332
+	client *Client
333
+
334
+	state    BrbState
335
+	duration time.Duration
336
+	timer    *time.Timer
337
+}
338
+
339
+func (bt *BrbTimer) Initialize(client *Client) {
340
+	bt.client = client
341
+}
342
+
343
+// attempts to enable BRB for a client, returns whether it succeeded
344
+func (bt *BrbTimer) Enable() (success bool, duration time.Duration) {
345
+	// BRB only makes sense if a new connection can attach to the session;
346
+	// this can happen either via RESUME or via bouncer reattach
347
+	if bt.client.Account() == "" && bt.client.ResumeID() == "" {
348
+		return
349
+	}
350
+
351
+	// TODO make this configurable
352
+	duration = ResumeableTotalTimeout
353
+
354
+	bt.client.stateMutex.Lock()
355
+	defer bt.client.stateMutex.Unlock()
356
+
357
+	switch bt.state {
358
+	case BrbDisabled, BrbEnabled:
359
+		bt.state = BrbEnabled
360
+		bt.duration = duration
361
+		bt.resetTimeout()
362
+		success = true
363
+	case BrbSticky:
364
+		success = true
365
+	default:
366
+		// BrbDead
367
+		success = false
368
+	}
369
+	return
370
+}
371
+
372
+// turns off BRB for a client and stops the timer; used on resume and during
373
+// client teardown
374
+func (bt *BrbTimer) Disable() {
375
+	bt.client.stateMutex.Lock()
376
+	defer bt.client.stateMutex.Unlock()
377
+
378
+	if bt.state == BrbEnabled {
379
+		bt.state = BrbDisabled
380
+	}
381
+	bt.resetTimeout()
382
+}
383
+
384
+func (bt *BrbTimer) resetTimeout() {
385
+	if bt.timer != nil {
386
+		bt.timer.Stop()
387
+	}
388
+	if bt.state != BrbEnabled {
389
+		return
390
+	}
391
+	if bt.timer == nil {
392
+		bt.timer = time.AfterFunc(bt.duration, bt.processTimeout)
393
+	} else {
394
+		bt.timer.Reset(bt.duration)
395
+	}
396
+}
397
+
398
+func (bt *BrbTimer) processTimeout() {
399
+	dead := false
400
+	defer func() {
401
+		if dead {
402
+			bt.client.Quit(bt.client.AwayMessage(), nil)
403
+			bt.client.destroy(nil)
404
+		}
405
+	}()
406
+
407
+	bt.client.stateMutex.Lock()
408
+	defer bt.client.stateMutex.Unlock()
409
+
410
+	switch bt.state {
411
+	case BrbDisabled, BrbEnabled:
412
+		if len(bt.client.sessions) == 0 {
413
+			// client never returned, quit them
414
+			bt.state = BrbDead
415
+			dead = true
416
+		} else {
417
+			// client resumed, reattached, or has another active session
418
+			bt.state = BrbDisabled
419
+		}
420
+	case BrbDead:
421
+		dead = true // shouldn't be possible but whatever
422
+	}
423
+	bt.resetTimeout()
424
+}
425
+
426
+// sets a client to be "sticky", i.e., indefinitely exempt from removal for
427
+// lack of sessions
428
+func (bt *BrbTimer) SetSticky() (success bool) {
429
+	bt.client.stateMutex.Lock()
430
+	defer bt.client.stateMutex.Unlock()
431
+	if bt.state != BrbDead {
432
+		success = true
433
+		bt.state = BrbSticky
434
+	}
435
+	bt.resetTimeout()
436
+	return
437
+}

+ 0
- 18
irc/monitor.go Zobrazit soubor

@@ -77,24 +77,6 @@ func (manager *MonitorManager) Remove(client *Client, nick string) error {
77 77
 	return nil
78 78
 }
79 79
 
80
-func (manager *MonitorManager) Resume(newClient, oldClient *Client) error {
81
-	manager.Lock()
82
-	defer manager.Unlock()
83
-
84
-	// newClient is now watching everyone oldClient was watching
85
-	oldTargets := manager.watching[oldClient]
86
-	delete(manager.watching, oldClient)
87
-	manager.watching[newClient] = oldTargets
88
-
89
-	// update watchedby as well
90
-	for watchedNick := range oldTargets {
91
-		delete(manager.watchedby[watchedNick], oldClient)
92
-		manager.watchedby[watchedNick][newClient] = true
93
-	}
94
-
95
-	return nil
96
-}
97
-
98 80
 // RemoveAll unregisters `client` from receiving notifications about *all* nicks.
99 81
 func (manager *MonitorManager) RemoveAll(client *Client) {
100 82
 	manager.Lock()

+ 1
- 1
irc/nickserv.go Zobrazit soubor

@@ -475,7 +475,7 @@ func nsGhostHandler(server *Server, client *Client, command string, params []str
475 475
 	}
476 476
 
477 477
 	ghost.Quit(fmt.Sprintf(ghost.t("GHOSTed by %s"), client.Nick()), nil)
478
-	ghost.destroy(false, nil)
478
+	ghost.destroy(nil)
479 479
 }
480 480
 
481 481
 func nsGroupHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {

+ 28
- 14
irc/resume.go Zobrazit soubor

@@ -9,7 +9,7 @@ import (
9 9
 	"github.com/oragono/oragono/irc/utils"
10 10
 )
11 11
 
12
-// implements draft/resume-0.3, in particular the issuing, management, and verification
12
+// implements draft/resume, in particular the issuing, management, and verification
13 13
 // of resume tokens with two components: a unique ID and a secret key
14 14
 
15 15
 type resumeTokenPair struct {
@@ -31,8 +31,8 @@ func (rm *ResumeManager) Initialize(server *Server) {
31 31
 
32 32
 // GenerateToken generates a resume token for a client. If the client has
33 33
 // already been assigned one, it returns "".
34
-func (rm *ResumeManager) GenerateToken(client *Client) (token string) {
35
-	id := utils.GenerateSecretToken()
34
+func (rm *ResumeManager) GenerateToken(client *Client) (token string, id string) {
35
+	id = utils.GenerateSecretToken()
36 36
 	secret := utils.GenerateSecretToken()
37 37
 
38 38
 	rm.Lock()
@@ -48,13 +48,13 @@ func (rm *ResumeManager) GenerateToken(client *Client) (token string) {
48 48
 		secret: secret,
49 49
 	}
50 50
 
51
-	return id + secret
51
+	return id + secret, id
52 52
 }
53 53
 
54 54
 // VerifyToken looks up the client corresponding to a resume token, returning
55 55
 // nil if there is no such client or the token is invalid. If successful,
56 56
 // the token is consumed and cannot be used to resume again.
57
-func (rm *ResumeManager) VerifyToken(token string) (client *Client) {
57
+func (rm *ResumeManager) VerifyToken(newClient *Client, token string) (oldClient *Client, id string) {
58 58
 	if len(token) != 2*utils.SecretTokenLength {
59 59
 		return
60 60
 	}
@@ -62,18 +62,32 @@ func (rm *ResumeManager) VerifyToken(token string) (client *Client) {
62 62
 	rm.Lock()
63 63
 	defer rm.Unlock()
64 64
 
65
-	id := token[:utils.SecretTokenLength]
65
+	id = token[:utils.SecretTokenLength]
66 66
 	pair, ok := rm.resumeIDtoCreds[id]
67
-	if ok {
68
-		if utils.SecretTokensMatch(pair.secret, token[utils.SecretTokenLength:]) {
69
-			// disallow resume of an unregistered client; this prevents the use of
70
-			// resume as an auth bypass
71
-			if pair.client.Registered() {
72
-				// consume the token, ensuring that at most one resume can succeed
73
-				delete(rm.resumeIDtoCreds, id)
74
-				return pair.client
67
+	if !ok {
68
+		return
69
+	}
70
+	// disallow resume of an unregistered client; this prevents the use of
71
+	// resume as an auth bypass
72
+	if !pair.client.Registered() {
73
+		return
74
+	}
75
+
76
+	if utils.SecretTokensMatch(pair.secret, token[utils.SecretTokenLength:]) {
77
+		oldClient = pair.client // success!
78
+		// consume the token, ensuring that at most one resume can succeed
79
+		delete(rm.resumeIDtoCreds, id)
80
+		// old client is henceforth resumeable under new client's creds (possibly empty)
81
+		newResumeID := newClient.ResumeID()
82
+		oldClient.SetResumeID(newResumeID)
83
+		if newResumeID != "" {
84
+			if newResumeCreds, ok := rm.resumeIDtoCreds[newResumeID]; ok {
85
+				newResumeCreds.client = oldClient
86
+				rm.resumeIDtoCreds[newResumeID] = newResumeCreds
75 87
 			}
76 88
 		}
89
+		// new client no longer "owns" newResumeID, remove the association
90
+		newClient.SetResumeID("")
77 91
 	}
78 92
 	return
79 93
 }

+ 32
- 38
irc/server.go Zobrazit soubor

@@ -331,42 +331,40 @@ func (server *Server) createListener(addr string, tlsConfig *tls.Config, isTor b
331 331
 //
332 332
 
333 333
 func (server *Server) tryRegister(c *Client, session *Session) {
334
-	resumed := false
335
-	// try to complete registration, either via RESUME token or normally
336
-	if c.resumeDetails != nil {
337
-		if !c.tryResume() {
338
-			return
339
-		}
340
-		resumed = true
341
-	} else {
342
-		if c.preregNick == "" || !c.HasUsername() || session.capState == caps.NegotiatingState {
343
-			return
344
-		}
334
+	// if the session just sent us a RESUME line, try to resume
335
+	if session.resumeDetails != nil {
336
+		session.tryResume()
337
+		return // whether we succeeded or failed, either way `c` is not getting registered
338
+	}
345 339
 
346
-		// client MUST send PASS if necessary, or authenticate with SASL if necessary,
347
-		// before completing the other registration commands
348
-		config := server.Config()
349
-		if !c.isAuthorized(config) {
350
-			c.Quit(c.t("Bad password"), nil)
351
-			c.destroy(false, nil)
352
-			return
353
-		}
340
+	// try to complete registration normally
341
+	if c.preregNick == "" || !c.HasUsername() || session.capState == caps.NegotiatingState {
342
+		return
343
+	}
354 344
 
355
-		rb := NewResponseBuffer(session)
356
-		nickAssigned := performNickChange(server, c, c, session, c.preregNick, rb)
357
-		rb.Send(true)
358
-		if !nickAssigned {
359
-			c.preregNick = ""
360
-			return
361
-		}
345
+	// client MUST send PASS if necessary, or authenticate with SASL if necessary,
346
+	// before completing the other registration commands
347
+	config := server.Config()
348
+	if !c.isAuthorized(config) {
349
+		c.Quit(c.t("Bad password"), nil)
350
+		c.destroy(nil)
351
+		return
352
+	}
362 353
 
363
-		// check KLINEs
364
-		isBanned, info := server.klines.CheckMasks(c.AllNickmasks()...)
365
-		if isBanned {
366
-			c.Quit(info.BanMessage(c.t("You are banned from this server (%s)")), nil)
367
-			c.destroy(false, nil)
368
-			return
369
-		}
354
+	rb := NewResponseBuffer(session)
355
+	nickAssigned := performNickChange(server, c, c, session, c.preregNick, rb)
356
+	rb.Send(true)
357
+	if !nickAssigned {
358
+		c.preregNick = ""
359
+		return
360
+	}
361
+
362
+	// check KLINEs
363
+	isBanned, info := server.klines.CheckMasks(c.AllNickmasks()...)
364
+	if isBanned {
365
+		c.Quit(info.BanMessage(c.t("You are banned from this server (%s)")), nil)
366
+		c.destroy(nil)
367
+		return
370 368
 	}
371 369
 
372 370
 	if session.client != c {
@@ -384,11 +382,7 @@ func (server *Server) tryRegister(c *Client, session *Session) {
384 382
 
385 383
 	server.playRegistrationBurst(session)
386 384
 
387
-	if resumed {
388
-		c.tryResumeChannels()
389
-	} else {
390
-		server.monitorManager.AlertAbout(c, true)
391
-	}
385
+	server.monitorManager.AlertAbout(c, true)
392 386
 }
393 387
 
394 388
 func (server *Server) playRegistrationBurst(session *Session) {

Načítá se…
Zrušit
Uložit