소스 검색

Merge pull request #509 from slingamn/brb.5

implement draft/resume-0.4
tags/v1.1.0-rc1
Daniel Oaks 5 년 전
부모
커밋
a27c46f983
No account linked to committer's email address
21개의 변경된 파일499개의 추가작업 그리고 402개의 파일을 삭제
  1. 1
    1
      gencapdefs.py
  2. 1
    1
      irc/accounts.go
  3. 2
    2
      irc/caps/defs.go
  4. 39
    54
      irc/channel.go
  5. 1
    1
      irc/channelmanager.go
  6. 120
    162
      irc/client.go
  7. 10
    36
      irc/client_lookup_set.go
  8. 4
    4
      irc/commands.go
  9. 20
    14
      irc/config.go
  10. 35
    23
      irc/getters.go
  11. 46
    22
      irc/handlers.go
  12. 8
    0
      irc/help.go
  13. 137
    2
      irc/idletimer.go
  14. 1
    1
      irc/modes.go
  15. 0
    18
      irc/monitor.go
  16. 1
    1
      irc/nickname.go
  17. 1
    1
      irc/nickserv.go
  18. 28
    14
      irc/resume.go
  19. 39
    44
      irc/server.go
  20. 1
    1
      irc/socket.go
  21. 4
    0
      oragono.yaml

+ 1
- 1
gencapdefs.py 파일 보기

@@ -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 파일 보기

@@ -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 파일 보기

@@ -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
 
@@ -141,7 +141,7 @@ var (
141 141
 		"message-tags",
142 142
 		"multi-prefix",
143 143
 		"draft/rename",
144
-		"draft/resume-0.3",
144
+		"draft/resume-0.4",
145 145
 		"sasl",
146 146
 		"server-time",
147 147
 		"draft/setname",

+ 39
- 54
irc/channel.go 파일 보기

@@ -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
@@ -664,10 +668,11 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
664 668
 func (channel *Channel) playJoinForSession(session *Session) {
665 669
 	client := session.client
666 670
 	sessionRb := NewResponseBuffer(session)
671
+	details := client.Details()
667 672
 	if session.capabilities.Has(caps.ExtendedJoin) {
668
-		sessionRb.Add(nil, client.NickMaskString(), "JOIN", channel.Name(), client.AccountName(), client.Realname())
673
+		sessionRb.Add(nil, details.nickMask, "JOIN", channel.Name(), details.accountName, details.realname)
669 674
 	} else {
670
-		sessionRb.Add(nil, client.NickMaskString(), "JOIN", channel.Name())
675
+		sessionRb.Add(nil, details.nickMask, "JOIN", channel.Name())
671 676
 	}
672 677
 	channel.SendTopic(client, sessionRb, false)
673 678
 	channel.Names(client, sessionRb)
@@ -711,46 +716,29 @@ func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer)
711 716
 // 1. Replace the old client with the new in the channel's data structures
712 717
 // 2. Send JOIN and MODE lines to channel participants (including the new client)
713 718
 // 3. Replay missed message history to the client
714
-func (channel *Channel) Resume(newClient, oldClient *Client, timestamp time.Time) {
719
+func (channel *Channel) Resume(session *Session, timestamp time.Time) {
715 720
 	now := time.Now().UTC()
716
-	channel.resumeAndAnnounce(newClient, oldClient)
721
+	channel.resumeAndAnnounce(session)
717 722
 	if !timestamp.IsZero() {
718
-		channel.replayHistoryForResume(newClient, timestamp, now)
723
+		channel.replayHistoryForResume(session, timestamp, now)
719 724
 	}
720 725
 }
721 726
 
722
-func (channel *Channel) resumeAndAnnounce(newClient, oldClient *Client) {
723
-	var oldModeSet *modes.ModeSet
724
-
725
-	func() {
726
-		channel.joinPartMutex.Lock()
727
-		defer channel.joinPartMutex.Unlock()
728
-
729
-		defer channel.regenerateMembersCache()
730
-
731
-		channel.stateMutex.Lock()
732
-		defer channel.stateMutex.Unlock()
733
-
734
-		newClient.channels[channel] = true
735
-		oldModeSet = channel.members[oldClient]
736
-		if oldModeSet == nil {
737
-			oldModeSet = modes.NewModeSet()
738
-		}
739
-		channel.members.Remove(oldClient)
740
-		channel.members[newClient] = oldModeSet
741
-	}()
742
-
743
-	// construct fake modestring if necessary
744
-	oldModes := oldModeSet.String()
727
+func (channel *Channel) resumeAndAnnounce(session *Session) {
728
+	channel.stateMutex.RLock()
729
+	modeSet := channel.members[session.client]
730
+	channel.stateMutex.RUnlock()
731
+	if modeSet == nil {
732
+		return
733
+	}
734
+	oldModes := modeSet.String()
745 735
 	if 0 < len(oldModes) {
746 736
 		oldModes = "+" + oldModes
747 737
 	}
748 738
 
749 739
 	// send join for old clients
750
-	nick := newClient.Nick()
751
-	nickMask := newClient.NickMaskString()
752
-	accountName := newClient.AccountName()
753
-	realName := newClient.Realname()
740
+	chname := channel.Name()
741
+	details := session.client.Details()
754 742
 	for _, member := range channel.Members() {
755 743
 		for _, session := range member.Sessions() {
756 744
 			if session.capabilities.Has(caps.Resume) {
@@ -758,39 +746,36 @@ func (channel *Channel) resumeAndAnnounce(newClient, oldClient *Client) {
758 746
 			}
759 747
 
760 748
 			if session.capabilities.Has(caps.ExtendedJoin) {
761
-				session.Send(nil, nickMask, "JOIN", channel.name, accountName, realName)
749
+				session.Send(nil, details.nickMask, "JOIN", chname, details.accountName, details.realname)
762 750
 			} else {
763
-				session.Send(nil, nickMask, "JOIN", channel.name)
751
+				session.Send(nil, details.nickMask, "JOIN", chname)
764 752
 			}
765 753
 
766 754
 			if 0 < len(oldModes) {
767
-				session.Send(nil, channel.server.name, "MODE", channel.name, oldModes, nick)
755
+				session.Send(nil, channel.server.name, "MODE", chname, oldModes, details.nick)
768 756
 			}
769 757
 		}
770 758
 	}
771 759
 
772
-	rb := NewResponseBuffer(newClient.Sessions()[0])
760
+	rb := NewResponseBuffer(session)
773 761
 	// use blocking i/o to synchronize with the later history replay
774 762
 	if rb.session.capabilities.Has(caps.ExtendedJoin) {
775
-		rb.Add(nil, nickMask, "JOIN", channel.name, accountName, realName)
763
+		rb.Add(nil, details.nickMask, "JOIN", channel.name, details.accountName, details.realname)
776 764
 	} else {
777
-		rb.Add(nil, nickMask, "JOIN", channel.name)
778
-	}
779
-	channel.SendTopic(newClient, rb, false)
780
-	channel.Names(newClient, rb)
781
-	if 0 < len(oldModes) {
782
-		rb.Add(nil, newClient.server.name, "MODE", channel.name, oldModes, nick)
765
+		rb.Add(nil, details.nickMask, "JOIN", channel.name)
783 766
 	}
767
+	channel.SendTopic(session.client, rb, false)
768
+	channel.Names(session.client, rb)
784 769
 	rb.Send(true)
785 770
 }
786 771
 
787
-func (channel *Channel) replayHistoryForResume(newClient *Client, after time.Time, before time.Time) {
772
+func (channel *Channel) replayHistoryForResume(session *Session, after time.Time, before time.Time) {
788 773
 	items, complete := channel.history.Between(after, before, false, 0)
789
-	rb := NewResponseBuffer(newClient.Sessions()[0])
774
+	rb := NewResponseBuffer(session)
790 775
 	channel.replayHistoryItems(rb, items, false)
791
-	if !complete && !newClient.resumeDetails.HistoryIncomplete {
776
+	if !complete && !session.resumeDetails.HistoryIncomplete {
792 777
 		// warn here if we didn't warn already
793
-		rb.Add(nil, "HistServ", "NOTICE", channel.Name(), newClient.t("Some additional message history may have been lost"))
778
+		rb.Add(nil, histServMask, "NOTICE", channel.Name(), session.client.t("Some additional message history may have been lost"))
794 779
 	}
795 780
 	rb.Send(true)
796 781
 }
@@ -844,7 +829,7 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
844 829
 				} else {
845 830
 					message = fmt.Sprintf(client.t("%[1]s [account: %[2]s] joined the channel"), nick, item.AccountName)
846 831
 				}
847
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), "HistServ", "*", nil, "PRIVMSG", chname, message)
832
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message)
848 833
 			}
849 834
 		case history.Part:
850 835
 			if eventPlayback {
@@ -854,14 +839,14 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
854 839
 					continue // #474
855 840
 				}
856 841
 				message := fmt.Sprintf(client.t("%[1]s left the channel (%[2]s)"), nick, item.Message.Message)
857
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), "HistServ", "*", nil, "PRIVMSG", chname, message)
842
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message)
858 843
 			}
859 844
 		case history.Kick:
860 845
 			if eventPlayback {
861 846
 				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "HEVENT", "KICK", chname, item.Params[0], item.Message.Message)
862 847
 			} else {
863 848
 				message := fmt.Sprintf(client.t("%[1]s kicked %[2]s (%[3]s)"), nick, item.Params[0], item.Message.Message)
864
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), "HistServ", "*", nil, "PRIVMSG", chname, message)
849
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message)
865 850
 			}
866 851
 		case history.Quit:
867 852
 			if eventPlayback {
@@ -871,14 +856,14 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
871 856
 					continue // #474
872 857
 				}
873 858
 				message := fmt.Sprintf(client.t("%[1]s quit (%[2]s)"), nick, item.Message.Message)
874
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), "HistServ", "*", nil, "PRIVMSG", chname, message)
859
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message)
875 860
 			}
876 861
 		case history.Nick:
877 862
 			if eventPlayback {
878 863
 				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "HEVENT", "NICK", item.Params[0])
879 864
 			} else {
880 865
 				message := fmt.Sprintf(client.t("%[1]s changed nick to %[2]s"), nick, item.Params[0])
881
-				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), "HistServ", "*", nil, "PRIVMSG", chname, message)
866
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message)
882 867
 			}
883 868
 		}
884 869
 	}
@@ -923,7 +908,7 @@ func (channel *Channel) SetTopic(client *Client, topic string, rb *ResponseBuffe
923 908
 		return
924 909
 	}
925 910
 
926
-	topicLimit := client.server.Limits().TopicLen
911
+	topicLimit := client.server.Config().Limits.TopicLen
927 912
 	if len(topic) > topicLimit {
928 913
 		topic = topic[:topicLimit]
929 914
 	}
@@ -1152,7 +1137,7 @@ func (channel *Channel) Kick(client *Client, target *Client, comment string, rb
1152 1137
 		return
1153 1138
 	}
1154 1139
 
1155
-	kicklimit := client.server.Limits().KickLen
1140
+	kicklimit := client.server.Config().Limits.KickLen
1156 1141
 	if len(comment) > kicklimit {
1157 1142
 		comment = comment[:kicklimit]
1158 1143
 	}

+ 1
- 1
irc/channelmanager.go 파일 보기

@@ -61,7 +61,7 @@ func (cm *ChannelManager) Get(name string) (channel *Channel) {
61 61
 func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin bool, rb *ResponseBuffer) error {
62 62
 	server := client.server
63 63
 	casefoldedName, err := CasefoldChannel(name)
64
-	if err != nil || len(casefoldedName) > server.Limits().ChannelLen {
64
+	if err != nil || len(casefoldedName) > server.Config().Limits.ChannelLen {
65 65
 		return errNoSuchChannel
66 66
 	}
67 67
 

+ 120
- 162
irc/client.go 파일 보기

@@ -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
@@ -114,6 +111,10 @@ type Session struct {
114 111
 	capState     caps.State
115 112
 	capVersion   caps.Version
116 113
 
114
+	registrationMessages int
115
+
116
+	resumeID         string
117
+	resumeDetails    *ResumeDetails
117 118
 	zncPlaybackTimes *zncPlaybackTimes
118 119
 }
119 120
 
@@ -209,8 +210,9 @@ func (server *Server) RunClient(conn clientConn) {
209 210
 		nick:           "*", // * is used until actual nick is given
210 211
 		nickCasefolded: "*",
211 212
 		nickMaskString: "*", // * is used until actual nick is given
212
-		history:        history.NewHistoryBuffer(config.History.ClientLength),
213 213
 	}
214
+	client.history.Initialize(config.History.ClientLength)
215
+	client.brbTimer.Initialize(client)
214 216
 	session := &Session{
215 217
 		client:     client,
216 218
 		socket:     socket,
@@ -334,14 +336,14 @@ func (client *Client) run(session *Session) {
334 336
 		if r := recover(); r != nil {
335 337
 			client.server.logger.Error("internal",
336 338
 				fmt.Sprintf("Client caused panic: %v\n%s", r, debug.Stack()))
337
-			if client.server.RecoverFromErrors() {
339
+			if client.server.Config().Debug.recoverFromErrors {
338 340
 				client.server.logger.Error("internal", "Disconnecting client and attempting to recover")
339 341
 			} else {
340 342
 				panic(r)
341 343
 			}
342 344
 		}
343 345
 		// ensure client connection gets closed
344
-		client.destroy(false, session)
346
+		client.destroy(session)
345 347
 	}()
346 348
 
347 349
 	session.idletimer.Initialize(session)
@@ -349,7 +351,13 @@ func (client *Client) run(session *Session) {
349 351
 
350 352
 	isReattach := client.Registered()
351 353
 	if isReattach {
352
-		client.playReattachMessages(session)
354
+		if session.resumeDetails != nil {
355
+			session.playResume()
356
+			session.resumeDetails = nil
357
+			client.brbTimer.Disable()
358
+		} else {
359
+			client.playReattachMessages(session)
360
+		}
353 361
 	} else {
354 362
 		// don't reset the nick timer during a reattach
355 363
 		client.nickTimer.Initialize(client)
@@ -367,6 +375,9 @@ func (client *Client) run(session *Session) {
367 375
 				quitMessage = "readQ exceeded"
368 376
 			}
369 377
 			client.Quit(quitMessage, session)
378
+			// since the client did not actually send us a QUIT,
379
+			// give them a chance to resume or reattach if applicable:
380
+			client.brbTimer.Enable()
370 381
 			break
371 382
 		}
372 383
 
@@ -387,6 +398,17 @@ func (client *Client) run(session *Session) {
387 398
 			}
388 399
 		}
389 400
 
401
+		if client.registered {
402
+			session.fakelag.Touch()
403
+		} else {
404
+			// DoS hardening, #505
405
+			session.registrationMessages++
406
+			if client.server.Config().Limits.RegistrationMessages < session.registrationMessages {
407
+				client.Send(nil, client.server.name, ERR_UNKNOWNERROR, "*", client.t("You have sent too many registration messages"))
408
+				break
409
+			}
410
+		}
411
+
390 412
 		msg, err := ircmsg.ParseLineStrict(line, true, maxlenRest)
391 413
 		if err == ircmsg.ErrorLineIsEmpty {
392 414
 			continue
@@ -445,83 +467,66 @@ func (session *Session) Ping() {
445 467
 }
446 468
 
447 469
 // tryResume tries to resume if the client asked us to.
448
-func (client *Client) tryResume() (success bool) {
449
-	server := client.server
450
-	config := server.Config()
470
+func (session *Session) tryResume() (success bool) {
471
+	var oldResumeID string
451 472
 
452 473
 	defer func() {
453
-		if !success {
454
-			client.resumeDetails = nil
474
+		if success {
475
+			// "On a successful request, the server [...] terminates the old client's connection"
476
+			oldSession := session.client.GetSessionByResumeID(oldResumeID)
477
+			if oldSession != nil {
478
+				session.client.destroy(oldSession)
479
+			}
480
+		} else {
481
+			session.resumeDetails = nil
455 482
 		}
456 483
 	}()
457 484
 
458
-	timestamp := client.resumeDetails.Timestamp
459
-	var timestampString string
460
-	if !timestamp.IsZero() {
461
-		timestampString = timestamp.UTC().Format(IRCv3TimestampFormat)
462
-	}
485
+	client := session.client
486
+	server := client.server
487
+	config := server.Config()
463 488
 
464
-	oldClient := server.resumeManager.VerifyToken(client.resumeDetails.PresentedToken)
489
+	oldClient, oldResumeID := server.resumeManager.VerifyToken(client, session.resumeDetails.PresentedToken)
465 490
 	if oldClient == nil {
466
-		client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection, token is not valid"))
491
+		session.Send(nil, server.name, "FAIL", "RESUME", "INVALID_TOKEN", client.t("Cannot resume connection, token is not valid"))
467 492
 		return
468 493
 	}
469
-	oldNick := oldClient.Nick()
470
-	oldNickmask := oldClient.NickMaskString()
471 494
 
472 495
 	resumeAllowed := config.Server.AllowPlaintextResume || (oldClient.HasMode(modes.TLS) && client.HasMode(modes.TLS))
473 496
 	if !resumeAllowed {
474
-		client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection, old and new clients must have TLS"))
497
+		session.Send(nil, server.name, "FAIL", "RESUME", "INSECURE_SESSION", client.t("Cannot resume connection, old and new clients must have TLS"))
475 498
 		return
476 499
 	}
477 500
 
478 501
 	if oldClient.isTor != client.isTor {
479
-		client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection from Tor to non-Tor or vice versa"))
502
+		session.Send(nil, server.name, "FAIL", "RESUME", "INSECURE_SESSION", client.t("Cannot resume connection from Tor to non-Tor or vice versa"))
480 503
 		return
481 504
 	}
482 505
 
483
-	if 1 < len(oldClient.Sessions()) {
484
-		client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume a client with multiple attached sessions"))
485
-		return
486
-	}
487
-
488
-	err := server.clients.Resume(client, oldClient)
506
+	err := server.clients.Resume(oldClient, session)
489 507
 	if err != nil {
490
-		client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection"))
508
+		session.Send(nil, server.name, "FAIL", "RESUME", "CANNOT_RESUME", client.t("Cannot resume connection"))
491 509
 		return
492 510
 	}
493 511
 
494 512
 	success = true
513
+	client.server.logger.Debug("quit", fmt.Sprintf("%s is being resumed", oldClient.Nick()))
495 514
 
496
-	// this is a bit racey
497
-	client.resumeDetails.ResumedAt = time.Now().UTC()
498
-
499
-	client.nickTimer.Touch(nil)
500
-
501
-	// resume successful, proceed to copy client state (nickname, flags, etc.)
502
-	// after this, the server thinks that `newClient` owns the nickname
503
-
504
-	client.resumeDetails.OldClient = oldClient
505
-
506
-	// transfer monitor stuff
507
-	server.monitorManager.Resume(client, oldClient)
508
-
509
-	// record the names, not the pointers, of the channels,
510
-	// to avoid dumb annoying race conditions
511
-	channels := oldClient.Channels()
512
-	client.resumeDetails.Channels = make([]string, len(channels))
513
-	for i, channel := range channels {
514
-		client.resumeDetails.Channels[i] = channel.Name()
515
-	}
515
+	return
516
+}
516 517
 
517
-	username := client.Username()
518
-	hostname := client.Hostname()
518
+// playResume is called from the session's fresh goroutine after a resume;
519
+// it sends notifications to friends, then plays the registration burst and replays
520
+// stored history to the session
521
+func (session *Session) playResume() {
522
+	client := session.client
523
+	server := client.server
519 524
 
520 525
 	friends := make(ClientSet)
521 526
 	oldestLostMessage := time.Now().UTC()
522 527
 
523 528
 	// work out how much time, if any, is not covered by history buffers
524
-	for _, channel := range channels {
529
+	for _, channel := range client.Channels() {
525 530
 		for _, member := range channel.Members() {
526 531
 			friends.Add(member)
527 532
 			lastDiscarded := channel.history.LastDiscarded()
@@ -533,8 +538,8 @@ func (client *Client) tryResume() (success bool) {
533 538
 	privmsgMatcher := func(item history.Item) bool {
534 539
 		return item.Type == history.Privmsg || item.Type == history.Notice || item.Type == history.Tagmsg
535 540
 	}
536
-	privmsgHistory := oldClient.history.Match(privmsgMatcher, false, 0)
537
-	lastDiscarded := oldClient.history.LastDiscarded()
541
+	privmsgHistory := client.history.Match(privmsgMatcher, false, 0)
542
+	lastDiscarded := client.history.LastDiscarded()
538 543
 	if lastDiscarded.Before(oldestLostMessage) {
539 544
 		oldestLostMessage = lastDiscarded
540 545
 	}
@@ -545,60 +550,61 @@ func (client *Client) tryResume() (success bool) {
545 550
 		}
546 551
 	}
547 552
 
553
+	timestamp := session.resumeDetails.Timestamp
548 554
 	gap := lastDiscarded.Sub(timestamp)
549
-	client.resumeDetails.HistoryIncomplete = gap > 0
555
+	session.resumeDetails.HistoryIncomplete = gap > 0
550 556
 	gapSeconds := int(gap.Seconds()) + 1 // round up to avoid confusion
551 557
 
558
+	details := client.Details()
559
+	oldNickmask := details.nickMask
560
+	client.SetRawHostname(session.rawHostname)
561
+	hostname := client.Hostname() // may be a vhost
562
+	timestampString := session.resumeDetails.Timestamp.Format(IRCv3TimestampFormat)
563
+
552 564
 	// send quit/resume messages to friends
553 565
 	for friend := range friends {
554
-		for _, session := range friend.Sessions() {
555
-			if session.capabilities.Has(caps.Resume) {
566
+		if friend == client {
567
+			continue
568
+		}
569
+		for _, fSession := range friend.Sessions() {
570
+			if fSession.capabilities.Has(caps.Resume) {
556 571
 				if timestamp.IsZero() {
557
-					session.Send(nil, oldNickmask, "RESUMED", username, hostname)
572
+					fSession.Send(nil, oldNickmask, "RESUMED", hostname)
558 573
 				} else {
559
-					session.Send(nil, oldNickmask, "RESUMED", username, hostname, timestampString)
574
+					fSession.Send(nil, oldNickmask, "RESUMED", hostname, timestampString)
560 575
 				}
561 576
 			} else {
562
-				if client.resumeDetails.HistoryIncomplete {
563
-					session.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected (up to %d seconds of history lost)"), gapSeconds))
577
+				if session.resumeDetails.HistoryIncomplete {
578
+					fSession.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected (up to %d seconds of history lost)"), gapSeconds))
564 579
 				} else {
565
-					session.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected")))
580
+					fSession.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected")))
566 581
 				}
567 582
 			}
568 583
 		}
569 584
 	}
570 585
 
571
-	if client.resumeDetails.HistoryIncomplete {
572
-		client.Send(nil, client.server.name, "RESUME", "WARN", fmt.Sprintf(client.t("Resume may have lost up to %d seconds of history"), gapSeconds))
586
+	if session.resumeDetails.HistoryIncomplete {
587
+		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))
573 588
 	}
574 589
 
575
-	client.Send(nil, client.server.name, "RESUME", "SUCCESS", oldNick)
590
+	session.Send(nil, client.server.name, "RESUME", "SUCCESS", details.nick)
576 591
 
577
-	// after we send the rest of the registration burst, we'll try rejoining channels
578
-	return
579
-}
580
-
581
-func (client *Client) tryResumeChannels() {
582
-	details := client.resumeDetails
592
+	server.playRegistrationBurst(session)
583 593
 
584
-	for _, name := range details.Channels {
585
-		channel := client.server.channels.Get(name)
586
-		if channel == nil {
587
-			continue
588
-		}
589
-		channel.Resume(client, details.OldClient, details.Timestamp)
594
+	for _, channel := range client.Channels() {
595
+		channel.Resume(session, timestamp)
590 596
 	}
591 597
 
592 598
 	// replay direct PRIVSMG history
593
-	if !details.Timestamp.IsZero() {
599
+	if !timestamp.IsZero() {
594 600
 		now := time.Now().UTC()
595
-		items, complete := client.history.Between(details.Timestamp, now, false, 0)
601
+		items, complete := client.history.Between(timestamp, now, false, 0)
596 602
 		rb := NewResponseBuffer(client.Sessions()[0])
597 603
 		client.replayPrivmsgHistory(rb, items, complete)
598 604
 		rb.Send(true)
599 605
 	}
600 606
 
601
-	details.OldClient.destroy(true, nil)
607
+	session.resumeDetails = nil
602 608
 }
603 609
 
604 610
 func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.Item, complete bool) {
@@ -646,41 +652,6 @@ func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.I
646 652
 	}
647 653
 }
648 654
 
649
-// copy applicable state from oldClient to client as part of a resume
650
-func (client *Client) copyResumeData(oldClient *Client) {
651
-	oldClient.stateMutex.RLock()
652
-	history := oldClient.history
653
-	nick := oldClient.nick
654
-	nickCasefolded := oldClient.nickCasefolded
655
-	vhost := oldClient.vhost
656
-	account := oldClient.account
657
-	accountName := oldClient.accountName
658
-	skeleton := oldClient.skeleton
659
-	oldClient.stateMutex.RUnlock()
660
-
661
-	// copy all flags, *except* TLS (in the case that the admins enabled
662
-	// resume over plaintext)
663
-	hasTLS := client.flags.HasMode(modes.TLS)
664
-	temp := modes.NewModeSet()
665
-	temp.Copy(&oldClient.flags)
666
-	temp.SetMode(modes.TLS, hasTLS)
667
-	client.flags.Copy(temp)
668
-
669
-	client.stateMutex.Lock()
670
-	defer client.stateMutex.Unlock()
671
-
672
-	// reuse the old client's history buffer
673
-	client.history = history
674
-	// copy other data
675
-	client.nick = nick
676
-	client.nickCasefolded = nickCasefolded
677
-	client.vhost = vhost
678
-	client.account = account
679
-	client.accountName = accountName
680
-	client.skeleton = skeleton
681
-	client.updateNickMaskNoMutex()
682
-}
683
-
684 655
 // IdleTime returns how long this client's been idle.
685 656
 func (client *Client) IdleTime() time.Duration {
686 657
 	client.stateMutex.RLock()
@@ -958,12 +929,13 @@ func (client *Client) Quit(message string, session *Session) {
958 929
 // if `session` is nil, destroys the client unconditionally, removing all sessions;
959 930
 // otherwise, destroys one specific session, only destroying the client if it
960 931
 // has no more sessions.
961
-func (client *Client) destroy(beingResumed bool, session *Session) {
932
+func (client *Client) destroy(session *Session) {
962 933
 	var sessionsToDestroy []*Session
963 934
 
964 935
 	// allow destroy() to execute at most once
965 936
 	client.stateMutex.Lock()
966 937
 	details := client.detailsNoMutex()
938
+	brbState := client.brbTimer.state
967 939
 	wasReattach := session != nil && session.client != client
968 940
 	sessionRemoved := false
969 941
 	var remainingSessions int
@@ -979,10 +951,6 @@ func (client *Client) destroy(beingResumed bool, session *Session) {
979 951
 	}
980 952
 	client.stateMutex.Unlock()
981 953
 
982
-	if len(sessionsToDestroy) == 0 {
983
-		return
984
-	}
985
-
986 954
 	// destroy all applicable sessions:
987 955
 	var quitMessage string
988 956
 	for _, session := range sessionsToDestroy {
@@ -1012,8 +980,8 @@ func (client *Client) destroy(beingResumed bool, session *Session) {
1012 980
 		client.server.logger.Info("localconnect-ip", fmt.Sprintf("disconnecting session of %s from %s", details.nick, source))
1013 981
 	}
1014 982
 
1015
-	// ok, now destroy the client, unless it still has sessions:
1016
-	if remainingSessions != 0 {
983
+	// do not destroy the client if it has either remaining sessions, or is BRB'ed
984
+	if remainingSessions != 0 || brbState == BrbEnabled || brbState == BrbSticky {
1017 985
 		return
1018 986
 	}
1019 987
 
@@ -1022,14 +990,12 @@ func (client *Client) destroy(beingResumed bool, session *Session) {
1022 990
 	client.server.semaphores.ClientDestroy.Acquire()
1023 991
 	defer client.server.semaphores.ClientDestroy.Release()
1024 992
 
1025
-	if beingResumed {
1026
-		client.server.logger.Debug("quit", fmt.Sprintf("%s is being resumed", details.nick))
1027
-	} else if !wasReattach {
993
+	if !wasReattach {
1028 994
 		client.server.logger.Debug("quit", fmt.Sprintf("%s is no longer on the server", details.nick))
1029 995
 	}
1030 996
 
1031 997
 	registered := client.Registered()
1032
-	if !beingResumed && registered {
998
+	if registered {
1033 999
 		client.server.whoWas.Append(client.WhoWas())
1034 1000
 	}
1035 1001
 
@@ -1047,15 +1013,13 @@ func (client *Client) destroy(beingResumed bool, session *Session) {
1047 1013
 	// (note that if this is a reattach, client has no channels and therefore no friends)
1048 1014
 	friends := make(ClientSet)
1049 1015
 	for _, channel := range client.Channels() {
1050
-		if !beingResumed {
1051
-			channel.Quit(client)
1052
-			channel.history.Add(history.Item{
1053
-				Type:        history.Quit,
1054
-				Nick:        details.nickMask,
1055
-				AccountName: details.accountName,
1056
-				Message:     splitQuitMessage,
1057
-			})
1058
-		}
1016
+		channel.Quit(client)
1017
+		channel.history.Add(history.Item{
1018
+			Type:        history.Quit,
1019
+			Nick:        details.nickMask,
1020
+			AccountName: details.accountName,
1021
+			Message:     splitQuitMessage,
1022
+		})
1059 1023
 		for _, member := range channel.Members() {
1060 1024
 			friends.Add(member)
1061 1025
 		}
@@ -1063,40 +1027,34 @@ func (client *Client) destroy(beingResumed bool, session *Session) {
1063 1027
 	friends.Remove(client)
1064 1028
 
1065 1029
 	// clean up server
1066
-	if !beingResumed {
1067
-		client.server.clients.Remove(client)
1068
-	}
1030
+	client.server.clients.Remove(client)
1069 1031
 
1070 1032
 	// clean up self
1071 1033
 	client.nickTimer.Stop()
1034
+	client.brbTimer.Disable()
1072 1035
 
1073 1036
 	client.server.accounts.Logout(client)
1074 1037
 
1075 1038
 	// send quit messages to friends
1076
-	if !beingResumed {
1077
-		if registered {
1078
-			client.server.stats.ChangeTotal(-1)
1079
-		}
1080
-		if client.HasMode(modes.Invisible) {
1081
-			client.server.stats.ChangeInvisible(-1)
1082
-		}
1083
-		if client.HasMode(modes.Operator) || client.HasMode(modes.LocalOperator) {
1084
-			client.server.stats.ChangeOperators(-1)
1085
-		}
1039
+	if registered {
1040
+		client.server.stats.ChangeTotal(-1)
1041
+	}
1042
+	if client.HasMode(modes.Invisible) {
1043
+		client.server.stats.ChangeInvisible(-1)
1044
+	}
1045
+	if client.HasMode(modes.Operator) || client.HasMode(modes.LocalOperator) {
1046
+		client.server.stats.ChangeOperators(-1)
1047
+	}
1086 1048
 
1087
-		for friend := range friends {
1088
-			if quitMessage == "" {
1089
-				quitMessage = "Exited"
1090
-			}
1091
-			friend.sendFromClientInternal(false, splitQuitMessage.Time, splitQuitMessage.Msgid, details.nickMask, details.accountName, nil, "QUIT", quitMessage)
1049
+	for friend := range friends {
1050
+		if quitMessage == "" {
1051
+			quitMessage = "Exited"
1092 1052
 		}
1053
+		friend.sendFromClientInternal(false, splitQuitMessage.Time, splitQuitMessage.Msgid, details.nickMask, details.accountName, nil, "QUIT", quitMessage)
1093 1054
 	}
1094
-	if !client.exitedSnomaskSent {
1095
-		if beingResumed {
1096
-			client.server.snomasks.Send(sno.LocalQuits, fmt.Sprintf(ircfmt.Unescape("%s$r is resuming their connection, old client has been destroyed"), client.nick))
1097
-		} else if registered {
1098
-			client.server.snomasks.Send(sno.LocalQuits, fmt.Sprintf(ircfmt.Unescape("%s$r exited the network"), details.nick))
1099
-		}
1055
+
1056
+	if !client.exitedSnomaskSent && registered {
1057
+		client.server.snomasks.Send(sno.LocalQuits, fmt.Sprintf(ircfmt.Unescape("%s$r exited the network"), details.nick))
1100 1058
 	}
1101 1059
 }
1102 1060
 

+ 10
- 36
irc/client_lookup_set.go 파일 보기

@@ -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
- 4
irc/commands.go 파일 보기

@@ -39,10 +39,6 @@ func (cmd *Command) Run(server *Server, client *Client, session *Session, msg ir
39 39
 		return false
40 40
 	}
41 41
 
42
-	if client.registered {
43
-		session.fakelag.Touch()
44
-	}
45
-
46 42
 	rb := NewResponseBuffer(session)
47 43
 	rb.Label = GetLabel(msg)
48 44
 	exiting := cmd.handler(server, client, msg, rb)
@@ -88,6 +84,10 @@ func init() {
88 84
 			handler:   awayHandler,
89 85
 			minParams: 0,
90 86
 		},
87
+		"BRB": {
88
+			handler:   brbHandler,
89
+			minParams: 0,
90
+		},
91 91
 		"CAP": {
92 92
 			handler:      capHandler,
93 93
 			usablePreReg: true,

+ 20
- 14
irc/config.go 파일 보기

@@ -214,16 +214,17 @@ type LineLenLimits struct {
214 214
 
215 215
 // Various server-enforced limits on data size.
216 216
 type Limits struct {
217
-	AwayLen        int           `yaml:"awaylen"`
218
-	ChanListModes  int           `yaml:"chan-list-modes"`
219
-	ChannelLen     int           `yaml:"channellen"`
220
-	IdentLen       int           `yaml:"identlen"`
221
-	KickLen        int           `yaml:"kicklen"`
222
-	LineLen        LineLenLimits `yaml:"linelen"`
223
-	MonitorEntries int           `yaml:"monitor-entries"`
224
-	NickLen        int           `yaml:"nicklen"`
225
-	TopicLen       int           `yaml:"topiclen"`
226
-	WhowasEntries  int           `yaml:"whowas-entries"`
217
+	AwayLen              int           `yaml:"awaylen"`
218
+	ChanListModes        int           `yaml:"chan-list-modes"`
219
+	ChannelLen           int           `yaml:"channellen"`
220
+	IdentLen             int           `yaml:"identlen"`
221
+	KickLen              int           `yaml:"kicklen"`
222
+	LineLen              LineLenLimits `yaml:"linelen"`
223
+	MonitorEntries       int           `yaml:"monitor-entries"`
224
+	NickLen              int           `yaml:"nicklen"`
225
+	TopicLen             int           `yaml:"topiclen"`
226
+	WhowasEntries        int           `yaml:"whowas-entries"`
227
+	RegistrationMessages int           `yaml:"registration-messages"`
227 228
 }
228 229
 
229 230
 // STSConfig controls the STS configuration/
@@ -334,7 +335,8 @@ type Config struct {
334 335
 	Logging []logger.LoggingConfig
335 336
 
336 337
 	Debug struct {
337
-		RecoverFromErrors *bool   `yaml:"recover-from-errors"`
338
+		RecoverFromErrors *bool `yaml:"recover-from-errors"`
339
+		recoverFromErrors bool
338 340
 		PprofListener     *string `yaml:"pprof-listener"`
339 341
 	}
340 342
 
@@ -532,6 +534,9 @@ func LoadConfig(filename string) (config *Config, err error) {
532 534
 	if config.Limits.NickLen < 1 || config.Limits.ChannelLen < 2 || config.Limits.AwayLen < 1 || config.Limits.KickLen < 1 || config.Limits.TopicLen < 1 {
533 535
 		return nil, ErrLimitsAreInsane
534 536
 	}
537
+	if config.Limits.RegistrationMessages == 0 {
538
+		config.Limits.RegistrationMessages = 1024
539
+	}
535 540
 	if config.Server.STS.Enabled {
536 541
 		config.Server.STS.Duration, err = custime.ParseDuration(config.Server.STS.DurationString)
537 542
 		if err != nil {
@@ -665,9 +670,10 @@ func LoadConfig(filename string) (config *Config, err error) {
665 670
 	}
666 671
 
667 672
 	// RecoverFromErrors defaults to true
668
-	if config.Debug.RecoverFromErrors == nil {
669
-		config.Debug.RecoverFromErrors = new(bool)
670
-		*config.Debug.RecoverFromErrors = true
673
+	if config.Debug.RecoverFromErrors != nil {
674
+		config.Debug.recoverFromErrors = *config.Debug.RecoverFromErrors
675
+	} else {
676
+		config.Debug.recoverFromErrors = true
671 677
 	}
672 678
 
673 679
 	// casefold/validate server name

+ 35
- 23
irc/getters.go 파일 보기

@@ -21,22 +21,6 @@ func (server *Server) SetConfig(config *Config) {
21 21
 	atomic.StorePointer(&server.config, unsafe.Pointer(config))
22 22
 }
23 23
 
24
-func (server *Server) Limits() Limits {
25
-	return server.Config().Limits
26
-}
27
-
28
-func (server *Server) Password() []byte {
29
-	return server.Config().Server.passwordBytes
30
-}
31
-
32
-func (server *Server) RecoverFromErrors() bool {
33
-	return *server.Config().Debug.RecoverFromErrors
34
-}
35
-
36
-func (server *Server) DefaultChannelModes() modes.Modes {
37
-	return server.Config().Channels.defaultModes
38
-}
39
-
40 24
 func (server *Server) ChannelRegistrationEnabled() bool {
41 25
 	return server.Config().Channels.Registration.Enabled
42 26
 }
@@ -65,6 +49,18 @@ func (client *Client) Sessions() (sessions []*Session) {
65 49
 	return
66 50
 }
67 51
 
52
+func (client *Client) GetSessionByResumeID(resumeID string) (result *Session) {
53
+	client.stateMutex.RLock()
54
+	defer client.stateMutex.RUnlock()
55
+
56
+	for _, session := range client.sessions {
57
+		if session.resumeID == resumeID {
58
+			return session
59
+		}
60
+	}
61
+	return
62
+}
63
+
68 64
 type SessionData struct {
69 65
 	ctime    time.Time
70 66
 	atime    time.Time
@@ -100,9 +96,17 @@ func (client *Client) AddSession(session *Session) (success bool) {
100 96
 	client.stateMutex.Lock()
101 97
 	defer client.stateMutex.Unlock()
102 98
 
103
-	if len(client.sessions) == 0 {
99
+	// client may be dying and ineligible to receive another session
100
+	switch client.brbTimer.state {
101
+	case BrbDisabled:
102
+		if len(client.sessions) == 0 {
103
+			return false
104
+		}
105
+	case BrbDead:
104 106
 		return false
107
+		// default: BrbEnabled or BrbSticky, proceed
105 108
 	}
109
+	// success, attach the new session to the client
106 110
 	session.client = client
107 111
 	client.sessions = append(client.sessions, session)
108 112
 	return true
@@ -125,6 +129,12 @@ func (client *Client) removeSession(session *Session) (success bool, length int)
125 129
 	return
126 130
 }
127 131
 
132
+func (session *Session) SetResumeID(resumeID string) {
133
+	session.client.stateMutex.Lock()
134
+	session.resumeID = resumeID
135
+	session.client.stateMutex.Unlock()
136
+}
137
+
128 138
 func (client *Client) Nick() string {
129 139
 	client.stateMutex.RLock()
130 140
 	defer client.stateMutex.RUnlock()
@@ -161,12 +171,6 @@ func (client *Client) Hostname() string {
161 171
 	return client.hostname
162 172
 }
163 173
 
164
-func (client *Client) Realname() string {
165
-	client.stateMutex.RLock()
166
-	defer client.stateMutex.RUnlock()
167
-	return client.realname
168
-}
169
-
170 174
 func (client *Client) Away() (result bool) {
171 175
 	client.stateMutex.Lock()
172 176
 	result = client.away
@@ -233,6 +237,14 @@ func (client *Client) RawHostname() (result string) {
233 237
 	return
234 238
 }
235 239
 
240
+func (client *Client) SetRawHostname(rawHostname string) {
241
+	client.stateMutex.Lock()
242
+	defer client.stateMutex.Unlock()
243
+
244
+	client.rawHostname = rawHostname
245
+	client.updateNickMaskNoMutex()
246
+}
247
+
236 248
 func (client *Client) AwayMessage() (result string) {
237 249
 	client.stateMutex.RLock()
238 250
 	result = client.awayMessage

+ 46
- 22
irc/handlers.go 파일 보기

@@ -475,7 +475,7 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
475 475
 	if len(msg.Params) > 0 {
476 476
 		isAway = true
477 477
 		awayMessage = msg.Params[0]
478
-		awayLen := server.Limits().AwayLen
478
+		awayLen := server.Config().Limits.AwayLen
479 479
 		if len(awayMessage) > awayLen {
480 480
 			awayMessage = awayMessage[:awayLen]
481 481
 		}
@@ -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
 
@@ -645,7 +671,7 @@ func chathistoryHandler(server *Server, client *Client, msg ircmsg.IrcMessage, r
645 671
 			myAccount := client.Account()
646 672
 			targetAccount := targetClient.Account()
647 673
 			if myAccount != "" && targetAccount != "" && myAccount == targetAccount {
648
-				hist = targetClient.history
674
+				hist = &targetClient.history
649 675
 			}
650 676
 		}
651 677
 	}
@@ -1031,7 +1057,7 @@ func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
1031 1057
 				killClient = true
1032 1058
 			} else {
1033 1059
 				// if mcl == client, we kill them below
1034
-				mcl.destroy(false, nil)
1060
+				mcl.destroy(nil)
1035 1061
 			}
1036 1062
 		}
1037 1063
 
@@ -1094,13 +1120,13 @@ func historyHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
1094 1120
 		hist = &channel.history
1095 1121
 	} else {
1096 1122
 		if strings.ToLower(target) == "me" {
1097
-			hist = client.history
1123
+			hist = &client.history
1098 1124
 		} else {
1099 1125
 			targetClient := server.clients.Get(target)
1100 1126
 			if targetClient != nil {
1101 1127
 				myAccount, targetAccount := client.Account(), targetClient.Account()
1102 1128
 				if myAccount != "" && targetAccount != "" && myAccount == targetAccount {
1103
-					hist = targetClient.history
1129
+					hist = &targetClient.history
1104 1130
 				}
1105 1131
 			}
1106 1132
 		}
@@ -1338,7 +1364,7 @@ func killHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
1338 1364
 	target.exitedSnomaskSent = true
1339 1365
 
1340 1366
 	target.Quit(quitMsg, nil)
1341
-	target.destroy(false, nil)
1367
+	target.destroy(nil)
1342 1368
 	return false
1343 1369
 }
1344 1370
 
@@ -1468,7 +1494,7 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
1468 1494
 				killClient = true
1469 1495
 			} else {
1470 1496
 				// if mcl == client, we kill them below
1471
-				mcl.destroy(false, nil)
1497
+				mcl.destroy(nil)
1472 1498
 			}
1473 1499
 		}
1474 1500
 
@@ -1808,7 +1834,7 @@ func monitorAddHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb
1808 1834
 	var online []string
1809 1835
 	var offline []string
1810 1836
 
1811
-	limits := server.Limits()
1837
+	limits := server.Config().Limits
1812 1838
 
1813 1839
 	targets := strings.Split(msg.Params[1], ",")
1814 1840
 	for _, target := range targets {
@@ -2196,7 +2222,7 @@ func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
2196 2222
 	}
2197 2223
 
2198 2224
 	// if no password exists, skip checking
2199
-	serverPassword := server.Password()
2225
+	serverPassword := server.Config().Server.passwordBytes
2200 2226
 	if serverPassword == nil {
2201 2227
 		return false
2202 2228
 	}
@@ -2299,12 +2325,13 @@ func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
2299 2325
 	// send RENAME messages
2300 2326
 	clientPrefix := client.NickMaskString()
2301 2327
 	for _, mcl := range channel.Members() {
2328
+		mDetails := mcl.Details()
2302 2329
 		for _, mSession := range mcl.Sessions() {
2303 2330
 			targetRb := rb
2304 2331
 			targetPrefix := clientPrefix
2305 2332
 			if mSession != rb.session {
2306 2333
 				targetRb = NewResponseBuffer(mSession)
2307
-				targetPrefix = mcl.NickMaskString()
2334
+				targetPrefix = mDetails.nickMask
2308 2335
 			}
2309 2336
 			if mSession.capabilities.Has(caps.Rename) {
2310 2337
 				if reason != "" {
@@ -2319,7 +2346,7 @@ func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
2319 2346
 					targetRb.Add(nil, targetPrefix, "PART", oldName, fmt.Sprintf(mcl.t("Channel renamed")))
2320 2347
 				}
2321 2348
 				if mSession.capabilities.Has(caps.ExtendedJoin) {
2322
-					targetRb.Add(nil, targetPrefix, "JOIN", newName, mcl.AccountName(), mcl.Realname())
2349
+					targetRb.Add(nil, targetPrefix, "JOIN", newName, mDetails.accountName, mDetails.realname)
2323 2350
 				} else {
2324 2351
 					targetRb.Add(nil, targetPrefix, "JOIN", newName)
2325 2352
 				}
@@ -2337,28 +2364,25 @@ func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
2337 2364
 
2338 2365
 // RESUME <token> [timestamp]
2339 2366
 func resumeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
2340
-	token := msg.Params[0]
2367
+	details := ResumeDetails{
2368
+		PresentedToken: msg.Params[0],
2369
+	}
2341 2370
 
2342 2371
 	if client.registered {
2343
-		rb.Add(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection, connection registration has already been completed"))
2372
+		rb.Add(nil, server.name, "FAIL", "RESUME", "REGISTRATION_IS_COMPLETED", client.t("Cannot resume connection, connection registration has already been completed"))
2344 2373
 		return false
2345 2374
 	}
2346 2375
 
2347
-	var timestamp time.Time
2348 2376
 	if 1 < len(msg.Params) {
2349 2377
 		ts, err := time.Parse(IRCv3TimestampFormat, msg.Params[1])
2350 2378
 		if err == nil {
2351
-			timestamp = ts
2379
+			details.Timestamp = ts
2352 2380
 		} else {
2353
-			rb.Add(nil, server.name, "RESUME", "WARN", client.t("Timestamp is not in 2006-01-02T15:04:05.999Z format, ignoring it"))
2381
+			rb.Add(nil, server.name, "WARN", "RESUME", "HISTORY_LOST", client.t("Timestamp is not in 2006-01-02T15:04:05.999Z format, ignoring it"))
2354 2382
 		}
2355 2383
 	}
2356 2384
 
2357
-	client.resumeDetails = &ResumeDetails{
2358
-		Timestamp:      timestamp,
2359
-		PresentedToken: token,
2360
-	}
2361
-
2385
+	rb.session.resumeDetails = &details
2362 2386
 	return false
2363 2387
 }
2364 2388
 

+ 8
- 0
irc/help.go 파일 보기

@@ -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 파일 보기

@@ -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
+}

+ 1
- 1
irc/modes.go 파일 보기

@@ -166,7 +166,7 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c
166 166
 
167 167
 			switch change.Op {
168 168
 			case modes.Add:
169
-				if channel.lists[change.Mode].Length() >= client.server.Limits().ChanListModes {
169
+				if channel.lists[change.Mode].Length() >= client.server.Config().Limits.ChanListModes {
170 170
 					if !listFullWarned[change.Mode] {
171 171
 						rb.Add(nil, client.server.name, ERR_BANLISTFULL, client.Nick(), channel.Name(), change.Mode.String(), client.t("Channel list is full"))
172 172
 						listFullWarned[change.Mode] = true

+ 0
- 18
irc/monitor.go 파일 보기

@@ -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/nickname.go 파일 보기

@@ -35,7 +35,7 @@ func performNickChange(server *Server, client *Client, target *Client, session *
35 35
 		return false
36 36
 	}
37 37
 
38
-	if err != nil || len(nickname) > server.Limits().NickLen || restrictedNicknames[cfnick] {
38
+	if err != nil || len(nickname) > server.Config().Limits.NickLen || restrictedNicknames[cfnick] {
39 39
 		rb.Add(nil, server.name, ERR_ERRONEUSNICKNAME, currentNick, nickname, client.t("Erroneous nickname"))
40 40
 		return false
41 41
 	}

+ 1
- 1
irc/nickserv.go 파일 보기

@@ -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 파일 보기

@@ -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
 }

+ 39
- 44
irc/server.go 파일 보기

@@ -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) {
@@ -503,26 +497,27 @@ func (client *Client) getWhoisOf(target *Client, rb *ResponseBuffer) {
503 497
 // rplWhoReply returns the WHO reply between one user and another channel/user.
504 498
 // <channel> <user> <host> <server> <nick> ( "H" / "G" ) ["*"] [ ( "@" / "+" ) ]
505 499
 // :<hopcount> <real name>
506
-func (target *Client) rplWhoReply(channel *Channel, client *Client, rb *ResponseBuffer) {
500
+func (client *Client) rplWhoReply(channel *Channel, target *Client, rb *ResponseBuffer) {
507 501
 	channelName := "*"
508 502
 	flags := ""
509 503
 
510
-	if client.Away() {
504
+	if target.Away() {
511 505
 		flags = "G"
512 506
 	} else {
513 507
 		flags = "H"
514 508
 	}
515
-	if client.HasMode(modes.Operator) {
509
+	if target.HasMode(modes.Operator) {
516 510
 		flags += "*"
517 511
 	}
518 512
 
519 513
 	if channel != nil {
520 514
 		// TODO is this right?
521
-		flags += channel.ClientPrefixes(client, rb.session.capabilities.Has(caps.MultiPrefix))
515
+		flags += channel.ClientPrefixes(target, rb.session.capabilities.Has(caps.MultiPrefix))
522 516
 		channelName = channel.name
523 517
 	}
518
+	details := target.Details()
524 519
 	// hardcode a hopcount of 0 for now
525
-	rb.Add(nil, target.server.name, RPL_WHOREPLY, target.nick, channelName, client.Username(), client.Hostname(), client.server.name, client.Nick(), flags, "0 "+client.Realname())
520
+	rb.Add(nil, client.server.name, RPL_WHOREPLY, client.Nick(), channelName, details.username, details.hostname, client.server.name, details.nick, flags, "0 "+details.realname)
526 521
 }
527 522
 
528 523
 // rehash reloads the config and applies the changes from the config file.
@@ -555,7 +550,7 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) {
555 550
 		server.nameCasefolded = config.Server.nameCasefolded
556 551
 	} else {
557 552
 		// enforce configs that can't be changed after launch:
558
-		currentLimits := server.Limits()
553
+		currentLimits := server.Config().Limits
559 554
 		if currentLimits.LineLen.Rest != config.Limits.LineLen.Rest {
560 555
 			return fmt.Errorf("Maximum line length (linelen) cannot be changed after launching the server, rehash aborted")
561 556
 		} else if server.name != config.Server.Name {

+ 1
- 1
irc/socket.go 파일 보기

@@ -254,7 +254,7 @@ func (socket *Socket) performWrite() (closed bool) {
254 254
 	socket.Unlock()
255 255
 
256 256
 	var err error
257
-	if !closed && len(buffers) > 0 {
257
+	if 0 < len(buffers) {
258 258
 		// on Linux, the runtime will optimize this into a single writev(2) call:
259 259
 		_, err = (*net.Buffers)(&buffers).WriteTo(socket.conn)
260 260
 	}

+ 4
- 0
oragono.yaml 파일 보기

@@ -557,6 +557,10 @@ limits:
557 557
         # configurable length for the rest of the message:
558 558
         rest: 2048
559 559
 
560
+    # maximum number of messages to accept during registration (prevents
561
+    # DoS / resource exhaustion attacks):
562
+    registration-messages: 1024
563
+
560 564
 # fakelag: prevents clients from spamming commands too rapidly
561 565
 fakelag:
562 566
     # whether to enforce fakelag

Loading…
취소
저장