Browse Source

initial implementation of bouncer functionality

tags/v1.1.0-rc1
Shivaram Lingamneni 5 years ago
parent
commit
c2faeed4b5
19 changed files with 733 additions and 441 deletions
  1. 12
    0
      gencapdefs.py
  2. 11
    4
      irc/accounts.go
  3. 11
    1
      irc/caps/defs.go
  4. 26
    0
      irc/caps/set.go
  5. 129
    66
      irc/channel.go
  6. 244
    174
      irc/client.go
  7. 23
    17
      irc/client_lookup_set.go
  8. 5
    5
      irc/commands.go
  9. 5
    1
      irc/config.go
  10. 6
    6
      irc/gateways.go
  11. 37
    6
      irc/getters.go
  12. 109
    76
      irc/handlers.go
  13. 14
    9
      irc/idletimer.go
  14. 15
    9
      irc/nickname.go
  15. 2
    2
      irc/nickserv.go
  16. 13
    11
      irc/responsebuffer.go
  17. 9
    8
      irc/roleplay.go
  18. 49
    46
      irc/server.go
  19. 13
    0
      oragono.yaml

+ 12
- 0
gencapdefs.py View File

147
         url="https://ircv3.net/specs/extensions/userhost-in-names-3.2.html",
147
         url="https://ircv3.net/specs/extensions/userhost-in-names-3.2.html",
148
         standard="IRCv3",
148
         standard="IRCv3",
149
     ),
149
     ),
150
+    CapDef(
151
+        identifier="Bouncer",
152
+        name="oragono.io/bnc",
153
+        url="https://oragono.io/bnc",
154
+        standard="Oragono-specific",
155
+    ),
156
+    CapDef(
157
+        identifier="ZNCSelfMessage",
158
+        name="znc.in/self-message",
159
+        url="https://wiki.znc.in/Query_buffers",
160
+        standard="ZNC vendor",
161
+    ),
150
 ]
162
 ]
151
 
163
 
152
 def validate_defs():
164
 def validate_defs():

+ 11
- 4
irc/accounts.go View File

221
 		nickMethod := finalEnforcementMethod(nickAccount)
221
 		nickMethod := finalEnforcementMethod(nickAccount)
222
 		skelMethod := finalEnforcementMethod(skelAccount)
222
 		skelMethod := finalEnforcementMethod(skelAccount)
223
 		switch {
223
 		switch {
224
-		case nickMethod == NickReservationNone && skelMethod == NickReservationNone:
225
-			return nickAccount, NickReservationNone
226
 		case skelMethod == NickReservationNone:
224
 		case skelMethod == NickReservationNone:
227
 			return nickAccount, nickMethod
225
 			return nickAccount, nickMethod
228
 		case nickMethod == NickReservationNone:
226
 		case nickMethod == NickReservationNone:
234
 	}
232
 	}
235
 }
233
 }
236
 
234
 
235
+func (am *AccountManager) BouncerAllowed(account string, session *Session) bool {
236
+	// TODO stub
237
+	config := am.server.Config()
238
+	if !config.Accounts.Bouncer.Enabled {
239
+		return false
240
+	}
241
+	return config.Accounts.Bouncer.AllowedByDefault || session.capabilities.Has(caps.Bouncer)
242
+}
243
+
237
 // Looks up the enforcement method stored in the database for an account
244
 // Looks up the enforcement method stored in the database for an account
238
 // (typically you want EnforcementStatus instead, which respects the config)
245
 // (typically you want EnforcementStatus instead, which respects the config)
239
 func (am *AccountManager) getStoredEnforcementStatus(account string) string {
246
 func (am *AccountManager) getStoredEnforcementStatus(account string) string {
928
 	}
935
 	}
929
 	for _, client := range clients {
936
 	for _, client := range clients {
930
 		if config.Accounts.RequireSasl.Enabled {
937
 		if config.Accounts.RequireSasl.Enabled {
931
-			client.Quit(client.t("You are no longer authorized to be on this server"))
938
+			client.Quit(client.t("You are no longer authorized to be on this server"), nil)
932
 			// destroy acquires a semaphore so we can't call it while holding a lock
939
 			// destroy acquires a semaphore so we can't call it while holding a lock
933
-			go client.destroy(false)
940
+			go client.destroy(false, nil)
934
 		} else {
941
 		} else {
935
 			am.logoutOfAccount(client)
942
 			am.logoutOfAccount(client)
936
 		}
943
 		}

+ 11
- 1
irc/caps/defs.go View File

7
 
7
 
8
 const (
8
 const (
9
 	// number of recognized capabilities:
9
 	// number of recognized capabilities:
10
-	numCapabs = 22
10
+	numCapabs = 24
11
 	// length of the uint64 array that represents the bitset:
11
 	// length of the uint64 array that represents the bitset:
12
 	bitsetLen = 1
12
 	bitsetLen = 1
13
 )
13
 )
100
 	// UserhostInNames is the IRCv3 capability named "userhost-in-names":
100
 	// UserhostInNames is the IRCv3 capability named "userhost-in-names":
101
 	// https://ircv3.net/specs/extensions/userhost-in-names-3.2.html
101
 	// https://ircv3.net/specs/extensions/userhost-in-names-3.2.html
102
 	UserhostInNames Capability = iota
102
 	UserhostInNames Capability = iota
103
+
104
+	// Bouncer is the Oragono-specific capability named "oragono.io/bnc":
105
+	// https://oragono.io/bnc
106
+	Bouncer Capability = iota
107
+
108
+	// ZNCSelfMessage is the ZNC vendor capability named "znc.in/self-message":
109
+	// https://wiki.znc.in/Query_buffers
110
+	ZNCSelfMessage Capability = iota
103
 )
111
 )
104
 
112
 
105
 // `capabilityNames[capab]` is the string name of the capability `capab`
113
 // `capabilityNames[capab]` is the string name of the capability `capab`
127
 		"draft/setname",
135
 		"draft/setname",
128
 		"sts",
136
 		"sts",
129
 		"userhost-in-names",
137
 		"userhost-in-names",
138
+		"oragono.io/bnc",
139
+		"znc.in/self-message",
130
 	}
140
 	}
131
 )
141
 )

+ 26
- 0
irc/caps/set.go View File

20
 	return &newSet
20
 	return &newSet
21
 }
21
 }
22
 
22
 
23
+// NewCompleteSet returns a new Set, with all defined capabilities enabled.
24
+func NewCompleteSet() *Set {
25
+	var newSet Set
26
+	asSlice := newSet[:]
27
+	for i := 0; i < numCapabs; i += 1 {
28
+		utils.BitsetSet(asSlice, uint(i), true)
29
+	}
30
+	return &newSet
31
+}
32
+
23
 // Enable enables the given capabilities.
33
 // Enable enables the given capabilities.
24
 func (s *Set) Enable(capabs ...Capability) {
34
 func (s *Set) Enable(capabs ...Capability) {
25
 	asSlice := s[:]
35
 	asSlice := s[:]
53
 	return utils.BitsetGet(s[:], uint(capab))
63
 	return utils.BitsetGet(s[:], uint(capab))
54
 }
64
 }
55
 
65
 
66
+// HasAll returns true if the set has all the given capabilities.
67
+func (s *Set) HasAll(capabs ...Capability) bool {
68
+	for _, capab := range capabs {
69
+		if !s.Has(capab) {
70
+			return false
71
+		}
72
+	}
73
+	return true
74
+}
75
+
56
 // Union adds all the capabilities of another set to this set.
76
 // Union adds all the capabilities of another set to this set.
57
 func (s *Set) Union(other *Set) {
77
 func (s *Set) Union(other *Set) {
58
 	utils.BitsetUnion(s[:], other[:])
78
 	utils.BitsetUnion(s[:], other[:])
94
 
114
 
95
 	return strings.Join(strs, " ")
115
 	return strings.Join(strs, " ")
96
 }
116
 }
117
+
118
+// returns whether we should send `znc.in/self-message`-style echo messages
119
+// to sessions other than that which originated the message
120
+func (capabs *Set) SelfMessagesEnabled() bool {
121
+	return capabs.Has(EchoMessage) || capabs.Has(ZNCSelfMessage)
122
+}

+ 129
- 66
irc/channel.go View File

335
 
335
 
336
 // Names sends the list of users joined to the channel to the given client.
336
 // Names sends the list of users joined to the channel to the given client.
337
 func (channel *Channel) Names(client *Client, rb *ResponseBuffer) {
337
 func (channel *Channel) Names(client *Client, rb *ResponseBuffer) {
338
-	isMultiPrefix := client.capabilities.Has(caps.MultiPrefix)
339
-	isUserhostInNames := client.capabilities.Has(caps.UserhostInNames)
338
+	isMultiPrefix := rb.session.capabilities.Has(caps.MultiPrefix)
339
+	isUserhostInNames := rb.session.capabilities.Has(caps.UserhostInNames)
340
 
340
 
341
 	maxNamLen := 480 - len(client.server.name) - len(client.Nick())
341
 	maxNamLen := 480 - len(client.server.name) - len(client.Nick())
342
 	var namesLines []string
342
 	var namesLines []string
578
 	}
578
 	}
579
 
579
 
580
 	for _, member := range channel.Members() {
580
 	for _, member := range channel.Members() {
581
-		if member == client {
582
-			continue
583
-		}
584
-		if member.capabilities.Has(caps.ExtendedJoin) {
585
-			member.Send(nil, details.nickMask, "JOIN", chname, details.accountName, details.realname)
586
-		} else {
587
-			member.Send(nil, details.nickMask, "JOIN", chname)
588
-		}
589
-		if givenMode != 0 {
590
-			member.Send(nil, client.server.name, "MODE", chname, modestr, details.nick)
581
+		for _, session := range member.Sessions() {
582
+			if session == rb.session {
583
+				continue
584
+			} else if client == session.client {
585
+				channel.playJoinForSession(session)
586
+				continue
587
+			}
588
+			if session.capabilities.Has(caps.ExtendedJoin) {
589
+				session.Send(nil, details.nickMask, "JOIN", chname, details.accountName, details.realname)
590
+			} else {
591
+				session.Send(nil, details.nickMask, "JOIN", chname)
592
+			}
593
+			if givenMode != 0 {
594
+				session.Send(nil, client.server.name, "MODE", chname, modestr, details.nick)
595
+			}
591
 		}
596
 		}
592
 	}
597
 	}
593
 
598
 
594
-	if client.capabilities.Has(caps.ExtendedJoin) {
599
+	if rb.session.capabilities.Has(caps.ExtendedJoin) {
595
 		rb.Add(nil, details.nickMask, "JOIN", chname, details.accountName, details.realname)
600
 		rb.Add(nil, details.nickMask, "JOIN", chname, details.accountName, details.realname)
596
 	} else {
601
 	} else {
597
 		rb.Add(nil, details.nickMask, "JOIN", chname)
602
 		rb.Add(nil, details.nickMask, "JOIN", chname)
598
 	}
603
 	}
599
 
604
 
600
-	channel.SendTopic(client, rb, false)
601
-
602
-	channel.Names(client, rb)
605
+	if rb.session.client == client {
606
+		// don't send topic and names for a SAJOIN of a different client
607
+		channel.SendTopic(client, rb, false)
608
+		channel.Names(client, rb)
609
+	}
603
 
610
 
604
 	// TODO #259 can be implemented as Flush(false) (i.e., nonblocking) while holding joinPartMutex
611
 	// TODO #259 can be implemented as Flush(false) (i.e., nonblocking) while holding joinPartMutex
605
 	rb.Flush(true)
612
 	rb.Flush(true)
612
 	}
619
 	}
613
 }
620
 }
614
 
621
 
622
+// plays channel join messages (the JOIN line, topic, and names) to a session.
623
+// this is used when attaching a new session to an existing client that already has
624
+// channels, and also when one session of a client initiates a JOIN and the other
625
+// sessions need to receive the state change
626
+func (channel *Channel) playJoinForSession(session *Session) {
627
+	client := session.client
628
+	sessionRb := NewResponseBuffer(session)
629
+	if session.capabilities.Has(caps.ExtendedJoin) {
630
+		sessionRb.Add(nil, client.NickMaskString(), "JOIN", channel.Name(), client.AccountName(), client.Realname())
631
+	} else {
632
+		sessionRb.Add(nil, client.NickMaskString(), "JOIN", channel.Name())
633
+	}
634
+	channel.SendTopic(client, sessionRb, false)
635
+	channel.Names(client, sessionRb)
636
+	sessionRb.Send(false)
637
+}
638
+
615
 // Part parts the given client from this channel, with the given message.
639
 // Part parts the given client from this channel, with the given message.
616
 func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer) {
640
 func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer) {
617
 	chname := channel.Name()
641
 	chname := channel.Name()
627
 		member.Send(nil, details.nickMask, "PART", chname, message)
651
 		member.Send(nil, details.nickMask, "PART", chname, message)
628
 	}
652
 	}
629
 	rb.Add(nil, details.nickMask, "PART", chname, message)
653
 	rb.Add(nil, details.nickMask, "PART", chname, message)
654
+	for _, session := range client.Sessions() {
655
+		if session != rb.session {
656
+			session.Send(nil, details.nickMask, "PART", chname, message)
657
+		}
658
+	}
630
 
659
 
631
 	channel.history.Add(history.Item{
660
 	channel.history.Add(history.Item{
632
 		Type:        history.Part,
661
 		Type:        history.Part,
683
 	accountName := newClient.AccountName()
712
 	accountName := newClient.AccountName()
684
 	realName := newClient.Realname()
713
 	realName := newClient.Realname()
685
 	for _, member := range channel.Members() {
714
 	for _, member := range channel.Members() {
686
-		if member.capabilities.Has(caps.Resume) {
687
-			continue
688
-		}
715
+		for _, session := range member.Sessions() {
716
+			if session.capabilities.Has(caps.Resume) {
717
+				continue
718
+			}
689
 
719
 
690
-		if member.capabilities.Has(caps.ExtendedJoin) {
691
-			member.Send(nil, nickMask, "JOIN", channel.name, accountName, realName)
692
-		} else {
693
-			member.Send(nil, nickMask, "JOIN", channel.name)
694
-		}
720
+			if session.capabilities.Has(caps.ExtendedJoin) {
721
+				session.Send(nil, nickMask, "JOIN", channel.name, accountName, realName)
722
+			} else {
723
+				session.Send(nil, nickMask, "JOIN", channel.name)
724
+			}
695
 
725
 
696
-		if 0 < len(oldModes) {
697
-			member.Send(nil, channel.server.name, "MODE", channel.name, oldModes, nick)
726
+			if 0 < len(oldModes) {
727
+				session.Send(nil, channel.server.name, "MODE", channel.name, oldModes, nick)
728
+			}
698
 		}
729
 		}
699
 	}
730
 	}
700
 
731
 
701
-	rb := NewResponseBuffer(newClient)
732
+	rb := NewResponseBuffer(newClient.Sessions()[0])
702
 	// use blocking i/o to synchronize with the later history replay
733
 	// use blocking i/o to synchronize with the later history replay
703
-	if newClient.capabilities.Has(caps.ExtendedJoin) {
734
+	if rb.session.capabilities.Has(caps.ExtendedJoin) {
704
 		rb.Add(nil, nickMask, "JOIN", channel.name, accountName, realName)
735
 		rb.Add(nil, nickMask, "JOIN", channel.name, accountName, realName)
705
 	} else {
736
 	} else {
706
 		rb.Add(nil, nickMask, "JOIN", channel.name)
737
 		rb.Add(nil, nickMask, "JOIN", channel.name)
715
 
746
 
716
 func (channel *Channel) replayHistoryForResume(newClient *Client, after time.Time, before time.Time) {
747
 func (channel *Channel) replayHistoryForResume(newClient *Client, after time.Time, before time.Time) {
717
 	items, complete := channel.history.Between(after, before, false, 0)
748
 	items, complete := channel.history.Between(after, before, false, 0)
718
-	rb := NewResponseBuffer(newClient)
749
+	rb := NewResponseBuffer(newClient.Sessions()[0])
719
 	channel.replayHistoryItems(rb, items)
750
 	channel.replayHistoryItems(rb, items)
720
 	if !complete && !newClient.resumeDetails.HistoryIncomplete {
751
 	if !complete && !newClient.resumeDetails.HistoryIncomplete {
721
 		// warn here if we didn't warn already
752
 		// warn here if we didn't warn already
735
 func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.Item) {
766
 func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.Item) {
736
 	chname := channel.Name()
767
 	chname := channel.Name()
737
 	client := rb.target
768
 	client := rb.target
738
-	serverTime := client.capabilities.Has(caps.ServerTime)
769
+	serverTime := rb.session.capabilities.Has(caps.ServerTime)
739
 
770
 
740
 	for _, item := range items {
771
 	for _, item := range items {
741
 		var tags map[string]string
772
 		var tags map[string]string
778
 // SendTopic sends the channel topic to the given client.
809
 // SendTopic sends the channel topic to the given client.
779
 // `sendNoTopic` controls whether RPL_NOTOPIC is sent when the topic is unset
810
 // `sendNoTopic` controls whether RPL_NOTOPIC is sent when the topic is unset
780
 func (channel *Channel) SendTopic(client *Client, rb *ResponseBuffer, sendNoTopic bool) {
811
 func (channel *Channel) SendTopic(client *Client, rb *ResponseBuffer, sendNoTopic bool) {
781
-	if !channel.hasClient(client) {
782
-		rb.Add(nil, client.server.name, ERR_NOTONCHANNEL, client.Nick(), channel.name, client.t("You're not on that channel"))
783
-		return
784
-	}
785
-
786
 	channel.stateMutex.RLock()
812
 	channel.stateMutex.RLock()
787
 	name := channel.name
813
 	name := channel.name
788
 	topic := channel.topic
814
 	topic := channel.topic
789
 	topicSetBy := channel.topicSetBy
815
 	topicSetBy := channel.topicSetBy
790
 	topicSetTime := channel.topicSetTime
816
 	topicSetTime := channel.topicSetTime
817
+	_, hasClient := channel.members[client]
791
 	channel.stateMutex.RUnlock()
818
 	channel.stateMutex.RUnlock()
792
 
819
 
820
+	if !hasClient {
821
+		rb.Add(nil, client.server.name, ERR_NOTONCHANNEL, client.Nick(), channel.name, client.t("You're not on that channel"))
822
+		return
823
+	}
824
+
793
 	if topic == "" {
825
 	if topic == "" {
794
 		if sendNoTopic {
826
 		if sendNoTopic {
795
 			rb.Add(nil, client.server.name, RPL_NOTOPIC, client.nick, name, client.t("No topic is set"))
827
 			rb.Add(nil, client.server.name, RPL_NOTOPIC, client.nick, name, client.t("No topic is set"))
824
 	channel.topicSetTime = time.Now()
856
 	channel.topicSetTime = time.Now()
825
 	channel.stateMutex.Unlock()
857
 	channel.stateMutex.Unlock()
826
 
858
 
859
+	prefix := client.NickMaskString()
827
 	for _, member := range channel.Members() {
860
 	for _, member := range channel.Members() {
828
-		if member == client {
829
-			rb.Add(nil, client.nickMaskString, "TOPIC", channel.name, topic)
830
-		} else {
831
-			member.Send(nil, client.nickMaskString, "TOPIC", channel.name, topic)
861
+		for _, session := range member.Sessions() {
862
+			if session == rb.session {
863
+				rb.Add(nil, prefix, "TOPIC", channel.name, topic)
864
+			} else {
865
+				session.Send(nil, prefix, "TOPIC", channel.name, topic)
866
+			}
832
 		}
867
 		}
833
 	}
868
 	}
834
 
869
 
880
 		return
915
 		return
881
 	}
916
 	}
882
 
917
 
918
+	nickmask := client.NickMaskString()
919
+	account := client.AccountName()
920
+	chname := channel.Name()
921
+	now := time.Now().UTC()
922
+
883
 	// for STATUSMSG
923
 	// for STATUSMSG
884
 	var minPrefixMode modes.Mode
924
 	var minPrefixMode modes.Mode
885
 	if minPrefix != nil {
925
 	if minPrefix != nil {
886
 		minPrefixMode = *minPrefix
926
 		minPrefixMode = *minPrefix
887
 	}
927
 	}
888
 	// send echo-message
928
 	// send echo-message
889
-	if client.capabilities.Has(caps.EchoMessage) {
929
+	// TODO this should use `now` as the time for consistency
930
+	if rb.session.capabilities.Has(caps.EchoMessage) {
890
 		var tagsToUse map[string]string
931
 		var tagsToUse map[string]string
891
-		if client.capabilities.Has(caps.MessageTags) {
932
+		if rb.session.capabilities.Has(caps.MessageTags) {
892
 			tagsToUse = clientOnlyTags
933
 			tagsToUse = clientOnlyTags
893
 		}
934
 		}
894
-		nickMaskString := client.NickMaskString()
895
-		accountName := client.AccountName()
896
-		if histType == history.Tagmsg && client.capabilities.Has(caps.MessageTags) {
897
-			rb.AddFromClient(message.Msgid, nickMaskString, accountName, tagsToUse, command, channel.name)
935
+		if histType == history.Tagmsg && rb.session.capabilities.Has(caps.MessageTags) {
936
+			rb.AddFromClient(message.Msgid, nickmask, account, tagsToUse, command, chname)
898
 		} else {
937
 		} else {
899
-			rb.AddSplitMessageFromClient(nickMaskString, accountName, tagsToUse, command, channel.name, message)
938
+			rb.AddSplitMessageFromClient(nickmask, account, tagsToUse, command, chname, message)
900
 		}
939
 		}
901
 	}
940
 	}
902
-
903
-	nickmask := client.NickMaskString()
904
-	account := client.AccountName()
905
-
906
-	now := time.Now().UTC()
907
-
908
-	for _, member := range channel.Members() {
909
-		if minPrefix != nil && !channel.ClientIsAtLeast(member, minPrefixMode) {
910
-			// STATUSMSG
941
+	// send echo-message to other connected sessions
942
+	for _, session := range client.Sessions() {
943
+		if session == rb.session || !session.capabilities.SelfMessagesEnabled() {
911
 			continue
944
 			continue
912
 		}
945
 		}
946
+		var tagsToUse map[string]string
947
+		if session.capabilities.Has(caps.MessageTags) {
948
+			tagsToUse = clientOnlyTags
949
+		}
950
+		if histType == history.Tagmsg && session.capabilities.Has(caps.MessageTags) {
951
+			session.sendFromClientInternal(false, now, message.Msgid, nickmask, account, tagsToUse, command, chname)
952
+		} else {
953
+			session.sendSplitMsgFromClientInternal(false, now, nickmask, account, tagsToUse, command, chname, message)
954
+		}
955
+	}
956
+
957
+	for _, member := range channel.Members() {
913
 		// echo-message is handled above, so skip sending the msg to the user themselves as well
958
 		// echo-message is handled above, so skip sending the msg to the user themselves as well
914
 		if member == client {
959
 		if member == client {
915
 			continue
960
 			continue
916
 		}
961
 		}
917
-		var tagsToUse map[string]string
918
-		if member.capabilities.Has(caps.MessageTags) {
919
-			tagsToUse = clientOnlyTags
920
-		} else if histType == history.Tagmsg {
962
+		if minPrefix != nil && !channel.ClientIsAtLeast(member, minPrefixMode) {
963
+			// STATUSMSG
921
 			continue
964
 			continue
922
 		}
965
 		}
923
 
966
 
924
-		if histType == history.Tagmsg {
925
-			member.sendFromClientInternal(false, now, message.Msgid, nickmask, account, tagsToUse, command, channel.name)
926
-		} else {
927
-			member.sendSplitMsgFromClientInternal(false, now, nickmask, account, tagsToUse, command, channel.name, message)
967
+		for _, session := range member.Sessions() {
968
+			var tagsToUse map[string]string
969
+			if session.capabilities.Has(caps.MessageTags) {
970
+				tagsToUse = clientOnlyTags
971
+			} else if histType == history.Tagmsg {
972
+				continue
973
+			}
974
+
975
+			if histType == history.Tagmsg {
976
+				session.sendFromClientInternal(false, now, message.Msgid, nickmask, account, tagsToUse, command, chname)
977
+			} else {
978
+				session.sendSplitMsgFromClientInternal(false, now, nickmask, account, tagsToUse, command, chname, message)
979
+			}
928
 		}
980
 		}
929
 	}
981
 	}
930
 
982
 
1059
 
1111
 
1060
 	clientMask := client.NickMaskString()
1112
 	clientMask := client.NickMaskString()
1061
 	targetNick := target.Nick()
1113
 	targetNick := target.Nick()
1114
+	chname := channel.Name()
1062
 	for _, member := range channel.Members() {
1115
 	for _, member := range channel.Members() {
1063
-		member.Send(nil, clientMask, "KICK", channel.name, targetNick, comment)
1116
+		for _, session := range member.Sessions() {
1117
+			if session != rb.session {
1118
+				session.Send(nil, clientMask, "KICK", chname, targetNick, comment)
1119
+			}
1120
+		}
1064
 	}
1121
 	}
1122
+	rb.Add(nil, clientMask, "KICK", chname, targetNick, comment)
1065
 
1123
 
1066
 	message := utils.SplitMessage{}
1124
 	message := utils.SplitMessage{}
1067
 	message.Message = comment
1125
 	message.Message = comment
1094
 	}
1152
 	}
1095
 
1153
 
1096
 	for _, member := range channel.Members() {
1154
 	for _, member := range channel.Members() {
1097
-		if member.capabilities.Has(caps.InviteNotify) && member != inviter && member != invitee && channel.ClientIsAtLeast(member, modes.Halfop) {
1098
-			member.Send(nil, inviter.NickMaskString(), "INVITE", invitee.Nick(), chname)
1155
+		if member == inviter || member == invitee || !channel.ClientIsAtLeast(member, modes.Halfop) {
1156
+			continue
1157
+		}
1158
+		for _, session := range member.Sessions() {
1159
+			if session.capabilities.Has(caps.InviteNotify) {
1160
+				session.Send(nil, inviter.NickMaskString(), "INVITE", invitee.Nick(), chname)
1161
+			}
1099
 		}
1162
 		}
1100
 	}
1163
 	}
1101
 
1164
 

+ 244
- 174
irc/client.go View File

50
 	accountName        string // display name of the account: uncasefolded, '*' if not logged in
50
 	accountName        string // display name of the account: uncasefolded, '*' if not logged in
51
 	atime              time.Time
51
 	atime              time.Time
52
 	awayMessage        string
52
 	awayMessage        string
53
-	capabilities       caps.Set
54
-	capState           caps.State
55
-	capVersion         caps.Version
56
 	certfp             string
53
 	certfp             string
57
 	channels           ChannelSet
54
 	channels           ChannelSet
58
 	ctime              time.Time
55
 	ctime              time.Time
59
 	exitedSnomaskSent  bool
56
 	exitedSnomaskSent  bool
60
-	fakelag            Fakelag
61
 	flags              modes.ModeSet
57
 	flags              modes.ModeSet
62
-	hasQuit            bool
63
-	hops               int
64
 	hostname           string
58
 	hostname           string
65
-	idletimer          IdleTimer
66
 	invitedTo          map[string]bool
59
 	invitedTo          map[string]bool
67
-	isDestroyed        bool
68
 	isTor              bool
60
 	isTor              bool
69
-	isQuitting         bool
70
 	languages          []string
61
 	languages          []string
71
 	loginThrottle      connection_limits.GenericThrottle
62
 	loginThrottle      connection_limits.GenericThrottle
72
-	maxlenRest         uint32
73
 	nick               string
63
 	nick               string
74
 	nickCasefolded     string
64
 	nickCasefolded     string
75
 	nickMaskCasefolded string
65
 	nickMaskCasefolded string
78
 	oper               *Oper
68
 	oper               *Oper
79
 	preregNick         string
69
 	preregNick         string
80
 	proxiedIP          net.IP // actual remote IP if using the PROXY protocol
70
 	proxiedIP          net.IP // actual remote IP if using the PROXY protocol
81
-	quitMessage        string
82
 	rawHostname        string
71
 	rawHostname        string
83
 	realname           string
72
 	realname           string
84
 	realIP             net.IP
73
 	realIP             net.IP
91
 	sentPassCommand    bool
80
 	sentPassCommand    bool
92
 	server             *Server
81
 	server             *Server
93
 	skeleton           string
82
 	skeleton           string
94
-	socket             *Socket
83
+	sessions           []*Session
95
 	stateMutex         sync.RWMutex // tier 1
84
 	stateMutex         sync.RWMutex // tier 1
96
 	username           string
85
 	username           string
97
 	vhost              string
86
 	vhost              string
98
 	history            *history.Buffer
87
 	history            *history.Buffer
99
 }
88
 }
100
 
89
 
90
+// Session is an individual client connection to the server (TCP connection
91
+// and associated per-connection data, such as capabilities). There is a
92
+// many-one relationship between sessions and clients.
93
+type Session struct {
94
+	client *Client
95
+
96
+	socket    *Socket
97
+	idletimer IdleTimer
98
+	fakelag   Fakelag
99
+
100
+	quitMessage string
101
+
102
+	capabilities caps.Set
103
+	maxlenRest   uint32
104
+	capState     caps.State
105
+	capVersion   caps.Version
106
+
107
+	// TODO track per-connection real IP, proxied IP, and hostname here,
108
+	// so we can list attached sessions and their details
109
+}
110
+
111
+// sets the session quit message, if there isn't one already
112
+func (sd *Session) SetQuitMessage(message string) (set bool) {
113
+	if message == "" {
114
+		if sd.quitMessage == "" {
115
+			sd.quitMessage = "Connection closed"
116
+			return true
117
+		} else {
118
+			return false
119
+		}
120
+	} else {
121
+		sd.quitMessage = message
122
+		return true
123
+	}
124
+}
125
+
126
+// set the negotiated message length based on session capabilities
127
+func (session *Session) SetMaxlenRest() {
128
+	maxlenRest := 512
129
+	if session.capabilities.Has(caps.MaxLine) {
130
+		maxlenRest = session.client.server.Config().Limits.LineLen.Rest
131
+	}
132
+	atomic.StoreUint32(&session.maxlenRest, uint32(maxlenRest))
133
+}
134
+
135
+// allow the negotiated message length limit to be read without locks; this is a convenience
136
+// so that Session.SendRawMessage doesn't have to acquire any Client locks
137
+func (session *Session) MaxlenRest() int {
138
+	return int(atomic.LoadUint32(&session.maxlenRest))
139
+}
140
+
101
 // WhoWas is the subset of client details needed to answer a WHOWAS query
141
 // WhoWas is the subset of client details needed to answer a WHOWAS query
102
 type WhoWas struct {
142
 type WhoWas struct {
103
 	nick           string
143
 	nick           string
125
 	// give them 1k of grace over the limit:
165
 	// give them 1k of grace over the limit:
126
 	socket := NewSocket(conn.Conn, fullLineLenLimit+1024, config.Server.MaxSendQBytes)
166
 	socket := NewSocket(conn.Conn, fullLineLenLimit+1024, config.Server.MaxSendQBytes)
127
 	client := &Client{
167
 	client := &Client{
128
-		atime:      now,
129
-		capState:   caps.NoneState,
130
-		capVersion: caps.Cap301,
131
-		channels:   make(ChannelSet),
132
-		ctime:      now,
133
-		isTor:      conn.IsTor,
134
-		languages:  server.Languages().Default(),
168
+		atime:     now,
169
+		channels:  make(ChannelSet),
170
+		ctime:     now,
171
+		isTor:     conn.IsTor,
172
+		languages: server.Languages().Default(),
135
 		loginThrottle: connection_limits.GenericThrottle{
173
 		loginThrottle: connection_limits.GenericThrottle{
136
 			Duration: config.Accounts.LoginThrottling.Duration,
174
 			Duration: config.Accounts.LoginThrottling.Duration,
137
 			Limit:    config.Accounts.LoginThrottling.MaxAttempts,
175
 			Limit:    config.Accounts.LoginThrottling.MaxAttempts,
138
 		},
176
 		},
139
 		server:         server,
177
 		server:         server,
140
-		socket:         socket,
141
 		accountName:    "*",
178
 		accountName:    "*",
142
 		nick:           "*", // * is used until actual nick is given
179
 		nick:           "*", // * is used until actual nick is given
143
 		nickCasefolded: "*",
180
 		nickCasefolded: "*",
144
 		nickMaskString: "*", // * is used until actual nick is given
181
 		nickMaskString: "*", // * is used until actual nick is given
145
 		history:        history.NewHistoryBuffer(config.History.ClientLength),
182
 		history:        history.NewHistoryBuffer(config.History.ClientLength),
146
 	}
183
 	}
147
-
148
-	client.recomputeMaxlens()
184
+	session := &Session{
185
+		client:     client,
186
+		socket:     socket,
187
+		capVersion: caps.Cap301,
188
+		capState:   caps.NoneState,
189
+	}
190
+	session.SetMaxlenRest()
191
+	client.sessions = []*Session{session}
149
 
192
 
150
 	if conn.IsTLS {
193
 	if conn.IsTLS {
151
 		client.SetMode(modes.TLS, true)
194
 		client.SetMode(modes.TLS, true)
152
 		// error is not useful to us here anyways so we can ignore it
195
 		// error is not useful to us here anyways so we can ignore it
153
-		client.certfp, _ = client.socket.CertFP()
196
+		client.certfp, _ = socket.CertFP()
154
 	}
197
 	}
155
 
198
 
156
 	if conn.IsTor {
199
 	if conn.IsTor {
168
 		}
211
 		}
169
 	}
212
 	}
170
 
213
 
171
-	client.run()
214
+	client.run(session)
172
 }
215
 }
173
 
216
 
174
 func (client *Client) doIdentLookup(conn net.Conn) {
217
 func (client *Client) doIdentLookup(conn net.Conn) {
214
 	return !config.Accounts.RequireSasl.Enabled || saslSent || utils.IPInNets(client.IP(), config.Accounts.RequireSasl.exemptedNets)
257
 	return !config.Accounts.RequireSasl.Enabled || saslSent || utils.IPInNets(client.IP(), config.Accounts.RequireSasl.exemptedNets)
215
 }
258
 }
216
 
259
 
217
-func (client *Client) resetFakelag() {
218
-	var flc FakelagConfig = client.server.Config().Fakelag
219
-	flc.Enabled = flc.Enabled && !client.HasRoleCapabs("nofakelag")
220
-	client.fakelag.Initialize(flc)
260
+func (session *Session) resetFakelag() {
261
+	var flc FakelagConfig = session.client.server.Config().Fakelag
262
+	flc.Enabled = flc.Enabled && !session.client.HasRoleCapabs("nofakelag")
263
+	session.fakelag.Initialize(flc)
221
 }
264
 }
222
 
265
 
223
 // IP returns the IP address of this client.
266
 // IP returns the IP address of this client.
244
 // command goroutine
287
 // command goroutine
245
 //
288
 //
246
 
289
 
247
-func (client *Client) recomputeMaxlens() int {
248
-	maxlenRest := 512
249
-	if client.capabilities.Has(caps.MaxLine) {
250
-		maxlenRest = client.server.Limits().LineLen.Rest
251
-	}
252
-
253
-	atomic.StoreUint32(&client.maxlenRest, uint32(maxlenRest))
254
-
255
-	return maxlenRest
256
-}
257
-
258
-// allow these negotiated length limits to be read without locks; this is a convenience
259
-// so that Client.Send doesn't have to acquire any Client locks
260
-func (client *Client) MaxlenRest() int {
261
-	return int(atomic.LoadUint32(&client.maxlenRest))
262
-}
263
-
264
-func (client *Client) run() {
265
-	var err error
266
-	var isExiting bool
267
-	var line string
268
-	var msg ircmsg.IrcMessage
290
+func (client *Client) run(session *Session) {
269
 
291
 
270
 	defer func() {
292
 	defer func() {
271
 		if r := recover(); r != nil {
293
 		if r := recover(); r != nil {
278
 			}
300
 			}
279
 		}
301
 		}
280
 		// ensure client connection gets closed
302
 		// ensure client connection gets closed
281
-		client.destroy(false)
303
+		client.destroy(false, session)
282
 	}()
304
 	}()
283
 
305
 
284
-	client.idletimer.Initialize(client)
306
+	session.idletimer.Initialize(session)
307
+	session.resetFakelag()
285
 
308
 
286
-	client.nickTimer.Initialize(client)
287
-
288
-	client.resetFakelag()
309
+	isReattach := client.Registered()
310
+	// don't reset the nick timer during a reattach
311
+	if !isReattach {
312
+		client.nickTimer.Initialize(client)
313
+	}
289
 
314
 
290
 	firstLine := true
315
 	firstLine := true
291
 
316
 
292
 	for {
317
 	for {
293
-		maxlenRest := client.recomputeMaxlens()
318
+		maxlenRest := session.MaxlenRest()
294
 
319
 
295
-		line, err = client.socket.Read()
320
+		line, err := session.socket.Read()
296
 		if err != nil {
321
 		if err != nil {
297
 			quitMessage := "connection closed"
322
 			quitMessage := "connection closed"
298
 			if err == errReadQ {
323
 			if err == errReadQ {
299
 				quitMessage = "readQ exceeded"
324
 				quitMessage = "readQ exceeded"
300
 			}
325
 			}
301
-			client.Quit(quitMessage)
326
+			client.Quit(quitMessage, session)
302
 			break
327
 			break
303
 		}
328
 		}
304
 
329
 
307
 		}
332
 		}
308
 
333
 
309
 		// special-cased handling of PROXY protocol, see `handleProxyCommand` for details:
334
 		// special-cased handling of PROXY protocol, see `handleProxyCommand` for details:
310
-		if firstLine {
335
+		if !isReattach && firstLine {
311
 			firstLine = false
336
 			firstLine = false
312
 			if strings.HasPrefix(line, "PROXY") {
337
 			if strings.HasPrefix(line, "PROXY") {
313
-				err = handleProxyCommand(client.server, client, line)
338
+				err = handleProxyCommand(client.server, client, session, line)
314
 				if err != nil {
339
 				if err != nil {
315
 					break
340
 					break
316
 				} else {
341
 				} else {
319
 			}
344
 			}
320
 		}
345
 		}
321
 
346
 
322
-		msg, err = ircmsg.ParseLineStrict(line, true, maxlenRest)
347
+		msg, err := ircmsg.ParseLineStrict(line, true, maxlenRest)
323
 		if err == ircmsg.ErrorLineIsEmpty {
348
 		if err == ircmsg.ErrorLineIsEmpty {
324
 			continue
349
 			continue
325
 		} else if err == ircmsg.ErrorLineTooLong {
350
 		} else if err == ircmsg.ErrorLineTooLong {
326
 			client.Send(nil, client.server.name, ERR_INPUTTOOLONG, client.Nick(), client.t("Input line too long"))
351
 			client.Send(nil, client.server.name, ERR_INPUTTOOLONG, client.Nick(), client.t("Input line too long"))
327
 			continue
352
 			continue
328
 		} else if err != nil {
353
 		} else if err != nil {
329
-			client.Quit(client.t("Received malformed line"))
354
+			client.Quit(client.t("Received malformed line"), session)
330
 			break
355
 			break
331
 		}
356
 		}
332
 
357
 
340
 			continue
365
 			continue
341
 		}
366
 		}
342
 
367
 
343
-		isExiting = cmd.Run(client.server, client, msg)
344
-		if isExiting || client.isQuitting {
368
+		isExiting := cmd.Run(client.server, client, session, msg)
369
+		if isExiting {
370
+			break
371
+		} else if session.client != client {
372
+			// bouncer reattach
373
+			session.playReattachMessages()
374
+			go session.client.run(session)
345
 			break
375
 			break
346
 		}
376
 		}
347
 	}
377
 	}
348
 }
378
 }
349
 
379
 
380
+func (session *Session) playReattachMessages() {
381
+	for _, channel := range session.client.Channels() {
382
+		channel.playJoinForSession(session)
383
+	}
384
+}
385
+
350
 //
386
 //
351
 // idle, quit, timers and timeouts
387
 // idle, quit, timers and timeouts
352
 //
388
 //
359
 }
395
 }
360
 
396
 
361
 // Ping sends the client a PING message.
397
 // Ping sends the client a PING message.
362
-func (client *Client) Ping() {
363
-	client.Send(nil, "", "PING", client.nick)
364
-
398
+func (session *Session) Ping() {
399
+	session.Send(nil, "", "PING", session.client.Nick())
365
 }
400
 }
366
 
401
 
367
 // tryResume tries to resume if the client asked us to.
402
 // tryResume tries to resume if the client asked us to.
400
 		return
435
 		return
401
 	}
436
 	}
402
 
437
 
438
+	if 1 < len(oldClient.Sessions()) {
439
+		client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume a client with multiple attached sessions"))
440
+		return
441
+	}
442
+
403
 	err := server.clients.Resume(client, oldClient)
443
 	err := server.clients.Resume(client, oldClient)
404
 	if err != nil {
444
 	if err != nil {
405
 		client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection"))
445
 		client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection"))
467
 
507
 
468
 	// send quit/resume messages to friends
508
 	// send quit/resume messages to friends
469
 	for friend := range friends {
509
 	for friend := range friends {
470
-		if friend.capabilities.Has(caps.Resume) {
471
-			if timestamp.IsZero() {
472
-				friend.Send(nil, oldNickmask, "RESUMED", username, hostname)
473
-			} else {
474
-				friend.Send(nil, oldNickmask, "RESUMED", username, hostname, timestampString)
475
-			}
476
-		} else {
477
-			if client.resumeDetails.HistoryIncomplete {
478
-				friend.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected (up to %d seconds of history lost)"), gapSeconds))
510
+		for _, session := range friend.Sessions() {
511
+			if session.capabilities.Has(caps.Resume) {
512
+				if timestamp.IsZero() {
513
+					session.Send(nil, oldNickmask, "RESUMED", username, hostname)
514
+				} else {
515
+					session.Send(nil, oldNickmask, "RESUMED", username, hostname, timestampString)
516
+				}
479
 			} else {
517
 			} else {
480
-				friend.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected")))
518
+				if client.resumeDetails.HistoryIncomplete {
519
+					session.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected (up to %d seconds of history lost)"), gapSeconds))
520
+				} else {
521
+					session.Send(nil, oldNickmask, "QUIT", fmt.Sprintf(friend.t("Client reconnected")))
522
+				}
481
 			}
523
 			}
482
 		}
524
 		}
483
 	}
525
 	}
509
 	if !details.Timestamp.IsZero() {
551
 	if !details.Timestamp.IsZero() {
510
 		now := time.Now()
552
 		now := time.Now()
511
 		items, complete := client.history.Between(details.Timestamp, now, false, 0)
553
 		items, complete := client.history.Between(details.Timestamp, now, false, 0)
512
-		rb := NewResponseBuffer(client)
554
+		rb := NewResponseBuffer(client.Sessions()[0])
513
 		client.replayPrivmsgHistory(rb, items, complete)
555
 		client.replayPrivmsgHistory(rb, items, complete)
514
 		rb.Send(true)
556
 		rb.Send(true)
515
 	}
557
 	}
516
 
558
 
517
-	details.OldClient.destroy(true)
559
+	details.OldClient.destroy(true, nil)
518
 }
560
 }
519
 
561
 
520
 func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.Item, complete bool) {
562
 func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.Item, complete bool) {
521
 	nick := client.Nick()
563
 	nick := client.Nick()
522
-	serverTime := client.capabilities.Has(caps.ServerTime)
564
+	serverTime := rb.session.capabilities.Has(caps.ServerTime)
523
 	for _, item := range items {
565
 	for _, item := range items {
524
 		var command string
566
 		var command string
525
 		switch item.Type {
567
 		switch item.Type {
661
 }
703
 }
662
 
704
 
663
 // Friends refers to clients that share a channel with this client.
705
 // Friends refers to clients that share a channel with this client.
664
-func (client *Client) Friends(capabs ...caps.Capability) ClientSet {
665
-	friends := make(ClientSet)
706
+func (client *Client) Friends(capabs ...caps.Capability) (result map[*Session]bool) {
707
+	result = make(map[*Session]bool)
666
 
708
 
667
-	// make sure that I have the right caps
668
-	hasCaps := true
669
-	for _, capab := range capabs {
670
-		if !client.capabilities.Has(capab) {
671
-			hasCaps = false
672
-			break
709
+	// look at the client's own sessions
710
+	for _, session := range client.Sessions() {
711
+		if session.capabilities.HasAll(capabs...) {
712
+			result[session] = true
673
 		}
713
 		}
674
 	}
714
 	}
675
-	if hasCaps {
676
-		friends.Add(client)
677
-	}
678
 
715
 
679
 	for _, channel := range client.Channels() {
716
 	for _, channel := range client.Channels() {
680
 		for _, member := range channel.Members() {
717
 		for _, member := range channel.Members() {
681
-			// make sure they have all the required caps
682
-			hasCaps = true
683
-			for _, capab := range capabs {
684
-				if !member.capabilities.Has(capab) {
685
-					hasCaps = false
686
-					break
718
+			for _, session := range member.Sessions() {
719
+				if session.capabilities.HasAll(capabs...) {
720
+					result[session] = true
687
 				}
721
 				}
688
 			}
722
 			}
689
-			if hasCaps {
690
-				friends.Add(member)
691
-			}
692
 		}
723
 		}
693
 	}
724
 	}
694
-	return friends
725
+
726
+	return
695
 }
727
 }
696
 
728
 
697
 func (client *Client) SetOper(oper *Oper) {
729
 func (client *Client) SetOper(oper *Oper) {
816
 // Quit sets the given quit message for the client.
848
 // Quit sets the given quit message for the client.
817
 // (You must ensure separately that destroy() is called, e.g., by returning `true` from
849
 // (You must ensure separately that destroy() is called, e.g., by returning `true` from
818
 // the command handler or calling it yourself.)
850
 // the command handler or calling it yourself.)
819
-func (client *Client) Quit(message string) {
820
-	client.stateMutex.Lock()
821
-	alreadyQuit := client.isQuitting
822
-	if !alreadyQuit {
823
-		client.isQuitting = true
824
-		client.quitMessage = message
825
-	}
826
-	registered := client.registered
827
-	prefix := client.nickMaskString
828
-	client.stateMutex.Unlock()
851
+func (client *Client) Quit(message string, session *Session) {
852
+	setFinalData := func(sess *Session) {
853
+		message := sess.quitMessage
854
+		var finalData []byte
855
+		// #364: don't send QUIT lines to unregistered clients
856
+		if client.registered {
857
+			quitMsg := ircmsg.MakeMessage(nil, client.nickMaskString, "QUIT", message)
858
+			finalData, _ = quitMsg.LineBytesStrict(false, 512)
859
+		}
829
 
860
 
830
-	if alreadyQuit {
831
-		return
832
-	}
861
+		errorMsg := ircmsg.MakeMessage(nil, "", "ERROR", message)
862
+		errorMsgBytes, _ := errorMsg.LineBytesStrict(false, 512)
863
+		finalData = append(finalData, errorMsgBytes...)
833
 
864
 
834
-	var finalData []byte
835
-	// #364: don't send QUIT lines to unregistered clients
836
-	if registered {
837
-		quitMsg := ircmsg.MakeMessage(nil, prefix, "QUIT", message)
838
-		finalData, _ = quitMsg.LineBytesStrict(false, 512)
865
+		sess.socket.SetFinalData(finalData)
839
 	}
866
 	}
840
 
867
 
841
-	errorMsg := ircmsg.MakeMessage(nil, "", "ERROR", message)
842
-	errorMsgBytes, _ := errorMsg.LineBytesStrict(false, 512)
843
-	finalData = append(finalData, errorMsgBytes...)
868
+	client.stateMutex.Lock()
869
+	defer client.stateMutex.Unlock()
870
+
871
+	var sessions []*Session
872
+	if session != nil {
873
+		sessions = []*Session{session}
874
+	} else {
875
+		sessions = client.sessions
876
+	}
844
 
877
 
845
-	client.socket.SetFinalData(finalData)
878
+	for _, session := range sessions {
879
+		if session.SetQuitMessage(message) {
880
+			setFinalData(session)
881
+		}
882
+	}
846
 }
883
 }
847
 
884
 
848
 // destroy gets rid of a client, removes them from server lists etc.
885
 // destroy gets rid of a client, removes them from server lists etc.
849
-func (client *Client) destroy(beingResumed bool) {
886
+// if `session` is nil, destroys the client unconditionally, removing all sessions;
887
+// otherwise, destroys one specific session, only destroying the client if it
888
+// has no more sessions.
889
+func (client *Client) destroy(beingResumed bool, session *Session) {
890
+	var sessionsToDestroy []*Session
891
+
850
 	// allow destroy() to execute at most once
892
 	// allow destroy() to execute at most once
851
 	client.stateMutex.Lock()
893
 	client.stateMutex.Lock()
852
-	isDestroyed := client.isDestroyed
853
-	client.isDestroyed = true
854
-	quitMessage := client.quitMessage
855
 	nickMaskString := client.nickMaskString
894
 	nickMaskString := client.nickMaskString
856
 	accountName := client.accountName
895
 	accountName := client.accountName
896
+
897
+	alreadyDestroyed := len(client.sessions) == 0
898
+	sessionRemoved := false
899
+	var remainingSessions int
900
+	if session == nil {
901
+		sessionRemoved = !alreadyDestroyed
902
+		sessionsToDestroy = client.sessions
903
+		client.sessions = nil
904
+		remainingSessions = 0
905
+	} else {
906
+		sessionRemoved, remainingSessions = client.removeSession(session)
907
+		if sessionRemoved {
908
+			sessionsToDestroy = []*Session{session}
909
+		}
910
+	}
911
+	var quitMessage string
912
+	if 0 < len(sessionsToDestroy) {
913
+		quitMessage = sessionsToDestroy[0].quitMessage
914
+	}
857
 	client.stateMutex.Unlock()
915
 	client.stateMutex.Unlock()
858
 
916
 
859
-	if isDestroyed {
917
+	if alreadyDestroyed || !sessionRemoved {
918
+		return
919
+	}
920
+
921
+	for _, session := range sessionsToDestroy {
922
+		if session.client != client {
923
+			// session has been attached to a new client; do not destroy it
924
+			continue
925
+		}
926
+		session.idletimer.Stop()
927
+		session.socket.Close()
928
+		// send quit/error message to client if they haven't been sent already
929
+		client.Quit("", session)
930
+	}
931
+
932
+	if remainingSessions != 0 {
860
 		return
933
 		return
861
 	}
934
 	}
862
 
935
 
871
 		client.server.logger.Debug("quit", fmt.Sprintf("%s is no longer on the server", client.nick))
944
 		client.server.logger.Debug("quit", fmt.Sprintf("%s is no longer on the server", client.nick))
872
 	}
945
 	}
873
 
946
 
874
-	// send quit/error message to client if they haven't been sent already
875
-	client.Quit("Connection closed")
876
-
877
 	if !beingResumed {
947
 	if !beingResumed {
878
 		client.server.whoWas.Append(client.WhoWas())
948
 		client.server.whoWas.Append(client.WhoWas())
879
 	}
949
 	}
916
 	}
986
 	}
917
 
987
 
918
 	// clean up self
988
 	// clean up self
919
-	client.idletimer.Stop()
920
 	client.nickTimer.Stop()
989
 	client.nickTimer.Stop()
921
 
990
 
922
 	client.server.accounts.Logout(client)
991
 	client.server.accounts.Logout(client)
923
 
992
 
924
-	client.socket.Close()
925
-
926
 	// send quit messages to friends
993
 	// send quit messages to friends
927
 	if !beingResumed {
994
 	if !beingResumed {
928
 		if client.Registered() {
995
 		if client.Registered() {
953
 
1020
 
954
 // SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client.
1021
 // SendSplitMsgFromClient sends an IRC PRIVMSG/NOTICE coming from a specific client.
955
 // Adds account-tag to the line as well.
1022
 // Adds account-tag to the line as well.
956
-func (client *Client) SendSplitMsgFromClient(from *Client, tags map[string]string, command, target string, message utils.SplitMessage) {
957
-	client.sendSplitMsgFromClientInternal(false, time.Time{}, from.NickMaskString(), from.AccountName(), tags, command, target, message)
958
-}
959
-
960
-func (client *Client) sendSplitMsgFromClientInternal(blocking bool, serverTime time.Time, nickmask, accountName string, tags map[string]string, command, target string, message utils.SplitMessage) {
961
-	if client.capabilities.Has(caps.MaxLine) || message.Wrapped == nil {
962
-		client.sendFromClientInternal(blocking, serverTime, message.Msgid, nickmask, accountName, tags, command, target, message.Message)
1023
+func (session *Session) sendSplitMsgFromClientInternal(blocking bool, serverTime time.Time, nickmask, accountName string, tags map[string]string, command, target string, message utils.SplitMessage) {
1024
+	if session.capabilities.Has(caps.MaxLine) || message.Wrapped == nil {
1025
+		session.sendFromClientInternal(blocking, serverTime, message.Msgid, nickmask, accountName, tags, command, target, message.Message)
963
 	} else {
1026
 	} else {
964
 		for _, messagePair := range message.Wrapped {
1027
 		for _, messagePair := range message.Wrapped {
965
-			client.sendFromClientInternal(blocking, serverTime, messagePair.Msgid, nickmask, accountName, tags, command, target, messagePair.Message)
1028
+			session.sendFromClientInternal(blocking, serverTime, messagePair.Msgid, nickmask, accountName, tags, command, target, messagePair.Message)
966
 		}
1029
 		}
967
 	}
1030
 	}
968
 }
1031
 }
976
 // this is SendFromClient, but directly exposing nickmask and accountName,
1039
 // this is SendFromClient, but directly exposing nickmask and accountName,
977
 // for things like history replay and CHGHOST where they no longer (necessarily)
1040
 // for things like history replay and CHGHOST where they no longer (necessarily)
978
 // correspond to the current state of a client
1041
 // correspond to the current state of a client
979
-func (client *Client) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) error {
1042
+func (client *Client) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) (err error) {
1043
+	for _, session := range client.Sessions() {
1044
+		err_ := session.sendFromClientInternal(blocking, serverTime, msgid, nickmask, accountName, tags, command, params...)
1045
+		if err_ != nil {
1046
+			err = err_
1047
+		}
1048
+	}
1049
+	return
1050
+}
1051
+
1052
+func (session *Session) sendFromClientInternal(blocking bool, serverTime time.Time, msgid string, nickmask, accountName string, tags map[string]string, command string, params ...string) (err error) {
980
 	msg := ircmsg.MakeMessage(tags, nickmask, command, params...)
1053
 	msg := ircmsg.MakeMessage(tags, nickmask, command, params...)
981
 	// attach account-tag
1054
 	// attach account-tag
982
-	if client.capabilities.Has(caps.AccountTag) && accountName != "*" {
1055
+	if session.capabilities.Has(caps.AccountTag) && accountName != "*" {
983
 		msg.SetTag("account", accountName)
1056
 		msg.SetTag("account", accountName)
984
 	}
1057
 	}
985
 	// attach message-id
1058
 	// attach message-id
986
-	if msgid != "" && client.capabilities.Has(caps.MessageTags) {
1059
+	if msgid != "" && session.capabilities.Has(caps.MessageTags) {
987
 		msg.SetTag("draft/msgid", msgid)
1060
 		msg.SetTag("draft/msgid", msgid)
988
 	}
1061
 	}
989
 	// attach server-time
1062
 	// attach server-time
990
-	if client.capabilities.Has(caps.ServerTime) {
1063
+	if session.capabilities.Has(caps.ServerTime) {
991
 		msg.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
1064
 		msg.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
992
 	}
1065
 	}
993
 
1066
 
994
-	return client.SendRawMessage(msg, blocking)
1067
+	return session.SendRawMessage(msg, blocking)
995
 }
1068
 }
996
 
1069
 
997
 var (
1070
 var (
1008
 )
1081
 )
1009
 
1082
 
1010
 // SendRawMessage sends a raw message to the client.
1083
 // SendRawMessage sends a raw message to the client.
1011
-func (client *Client) SendRawMessage(message ircmsg.IrcMessage, blocking bool) error {
1084
+func (session *Session) SendRawMessage(message ircmsg.IrcMessage, blocking bool) error {
1012
 	// use dumb hack to force the last param to be a trailing param if required
1085
 	// use dumb hack to force the last param to be a trailing param if required
1013
 	var usedTrailingHack bool
1086
 	var usedTrailingHack bool
1014
 	if commandsThatMustUseTrailing[message.Command] && len(message.Params) > 0 {
1087
 	if commandsThatMustUseTrailing[message.Command] && len(message.Params) > 0 {
1021
 	}
1094
 	}
1022
 
1095
 
1023
 	// assemble message
1096
 	// assemble message
1024
-	maxlenRest := client.MaxlenRest()
1097
+	maxlenRest := session.MaxlenRest()
1025
 	line, err := message.LineBytesStrict(false, maxlenRest)
1098
 	line, err := message.LineBytesStrict(false, maxlenRest)
1026
 	if err != nil {
1099
 	if err != nil {
1027
 		logline := fmt.Sprintf("Error assembling message for sending: %v\n%s", err, debug.Stack())
1100
 		logline := fmt.Sprintf("Error assembling message for sending: %v\n%s", err, debug.Stack())
1028
-		client.server.logger.Error("internal", logline)
1101
+		session.client.server.logger.Error("internal", logline)
1029
 
1102
 
1030
-		message = ircmsg.MakeMessage(nil, client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
1103
+		message = ircmsg.MakeMessage(nil, session.client.server.name, ERR_UNKNOWNERROR, "*", "Error assembling message for sending")
1031
 		line, _ := message.LineBytesStrict(false, 0)
1104
 		line, _ := message.LineBytesStrict(false, 0)
1032
 
1105
 
1033
 		if blocking {
1106
 		if blocking {
1034
-			client.socket.BlockingWrite(line)
1107
+			session.socket.BlockingWrite(line)
1035
 		} else {
1108
 		} else {
1036
-			client.socket.Write(line)
1109
+			session.socket.Write(line)
1037
 		}
1110
 		}
1038
 		return err
1111
 		return err
1039
 	}
1112
 	}
1044
 		line = line[:len(line)-1]
1117
 		line = line[:len(line)-1]
1045
 	}
1118
 	}
1046
 
1119
 
1047
-	if client.server.logger.IsLoggingRawIO() {
1120
+	if session.client.server.logger.IsLoggingRawIO() {
1048
 		logline := string(line[:len(line)-2]) // strip "\r\n"
1121
 		logline := string(line[:len(line)-2]) // strip "\r\n"
1049
-		client.server.logger.Debug("useroutput", client.nick, " ->", logline)
1122
+		session.client.server.logger.Debug("useroutput", session.client.Nick(), " ->", logline)
1050
 	}
1123
 	}
1051
 
1124
 
1052
 	if blocking {
1125
 	if blocking {
1053
-		return client.socket.BlockingWrite(line)
1126
+		return session.socket.BlockingWrite(line)
1054
 	} else {
1127
 	} else {
1055
-		return client.socket.Write(line)
1128
+		return session.socket.Write(line)
1056
 	}
1129
 	}
1057
 }
1130
 }
1058
 
1131
 
1059
 // Send sends an IRC line to the client.
1132
 // Send sends an IRC line to the client.
1060
-func (client *Client) Send(tags map[string]string, prefix string, command string, params ...string) error {
1133
+func (client *Client) Send(tags map[string]string, prefix string, command string, params ...string) (err error) {
1134
+	for _, session := range client.Sessions() {
1135
+		err_ := session.Send(tags, prefix, command, params...)
1136
+		if err_ != nil {
1137
+			err = err_
1138
+		}
1139
+	}
1140
+	return
1141
+}
1142
+
1143
+func (session *Session) Send(tags map[string]string, prefix string, command string, params ...string) (err error) {
1061
 	msg := ircmsg.MakeMessage(tags, prefix, command, params...)
1144
 	msg := ircmsg.MakeMessage(tags, prefix, command, params...)
1062
-	if client.capabilities.Has(caps.ServerTime) && !msg.HasTag("time") {
1145
+	if session.capabilities.Has(caps.ServerTime) && !msg.HasTag("time") {
1063
 		msg.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
1146
 		msg.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
1064
 	}
1147
 	}
1065
-	return client.SendRawMessage(msg, false)
1148
+	return session.SendRawMessage(msg, false)
1066
 }
1149
 }
1067
 
1150
 
1068
 // Notice sends the client a notice from the server.
1151
 // Notice sends the client a notice from the server.
1069
 func (client *Client) Notice(text string) {
1152
 func (client *Client) Notice(text string) {
1070
-	limit := 400
1071
-	if client.capabilities.Has(caps.MaxLine) {
1072
-		limit = client.server.Limits().LineLen.Rest - 110
1073
-	}
1074
-	lines := utils.WordWrap(text, limit)
1075
-
1076
-	// force blank lines to be sent if we receive them
1077
-	if len(lines) == 0 {
1078
-		lines = []string{""}
1079
-	}
1080
-
1081
-	for _, line := range lines {
1082
-		client.Send(nil, client.server.name, "NOTICE", client.nick, line)
1083
-	}
1153
+	client.Send(nil, client.server.name, "NOTICE", client.Nick(), text)
1084
 }
1154
 }
1085
 
1155
 
1086
 func (client *Client) addChannel(channel *Channel) {
1156
 func (client *Client) addChannel(channel *Channel) {

+ 23
- 17
irc/client_lookup_set.go View File

11
 	"strings"
11
 	"strings"
12
 
12
 
13
 	"github.com/goshuirc/irc-go/ircmatch"
13
 	"github.com/goshuirc/irc-go/ircmatch"
14
+
14
 	"github.com/oragono/oragono/irc/caps"
15
 	"github.com/oragono/oragono/irc/caps"
16
+	"github.com/oragono/oragono/irc/modes"
15
 
17
 
16
 	"sync"
18
 	"sync"
17
 )
19
 )
131
 }
133
 }
132
 
134
 
133
 // SetNick sets a client's nickname, validating it against nicknames in use
135
 // SetNick sets a client's nickname, validating it against nicknames in use
134
-func (clients *ClientManager) SetNick(client *Client, newNick string) error {
136
+func (clients *ClientManager) SetNick(client *Client, session *Session, newNick string) error {
135
 	newcfnick, err := CasefoldName(newNick)
137
 	newcfnick, err := CasefoldName(newNick)
136
 	if err != nil {
138
 	if err != nil {
137
 		return err
139
 		return err
142
 	}
144
 	}
143
 
145
 
144
 	reservedAccount, method := client.server.accounts.EnforcementStatus(newcfnick, newSkeleton)
146
 	reservedAccount, method := client.server.accounts.EnforcementStatus(newcfnick, newSkeleton)
147
+	account := client.Account()
148
+	bouncerAllowed := client.server.accounts.BouncerAllowed(account, session)
145
 
149
 
146
 	clients.Lock()
150
 	clients.Lock()
147
 	defer clients.Unlock()
151
 	defer clients.Unlock()
148
 
152
 
149
-	currentNewEntry := clients.byNick[newcfnick]
153
+	currentClient := clients.byNick[newcfnick]
150
 	// the client may just be changing case
154
 	// the client may just be changing case
151
-	if currentNewEntry != nil && currentNewEntry != client {
152
-		return errNicknameInUse
155
+	if currentClient != nil && currentClient != client {
156
+		// these conditions forbid reattaching to an existing session:
157
+		if client.Registered() || !bouncerAllowed || account == "" || account != currentClient.Account() || client.isTor != currentClient.isTor || client.HasMode(modes.TLS) != currentClient.HasMode(modes.TLS) {
158
+			return errNicknameInUse
159
+		}
160
+		if !currentClient.AddSession(session) {
161
+			return errNicknameInUse
162
+		}
163
+		// successful reattach:
164
+		return nil
153
 	}
165
 	}
154
 	// analogous checks for skeletons
166
 	// analogous checks for skeletons
155
 	skeletonHolder := clients.bySkeleton[newSkeleton]
167
 	skeletonHolder := clients.bySkeleton[newSkeleton]
156
 	if skeletonHolder != nil && skeletonHolder != client {
168
 	if skeletonHolder != nil && skeletonHolder != client {
157
 		return errNicknameInUse
169
 		return errNicknameInUse
158
 	}
170
 	}
159
-	if method == NickReservationStrict && reservedAccount != "" && reservedAccount != client.Account() {
171
+	if method == NickReservationStrict && reservedAccount != "" && reservedAccount != account {
160
 		return errNicknameReserved
172
 		return errNicknameReserved
161
 	}
173
 	}
162
 	clients.removeInternal(client)
174
 	clients.removeInternal(client)
179
 }
191
 }
180
 
192
 
181
 // AllWithCaps returns all clients with the given capabilities.
193
 // AllWithCaps returns all clients with the given capabilities.
182
-func (clients *ClientManager) AllWithCaps(capabs ...caps.Capability) (set ClientSet) {
183
-	set = make(ClientSet)
184
-
194
+func (clients *ClientManager) AllWithCaps(capabs ...caps.Capability) (sessions []*Session) {
185
 	clients.RLock()
195
 	clients.RLock()
186
 	defer clients.RUnlock()
196
 	defer clients.RUnlock()
187
-	var client *Client
188
-	for _, client = range clients.byNick {
189
-		// make sure they have all the required caps
190
-		for _, capab := range capabs {
191
-			if !client.capabilities.Has(capab) {
192
-				continue
197
+	for _, client := range clients.byNick {
198
+		for _, session := range client.Sessions() {
199
+			if session.capabilities.HasAll(capabs...) {
200
+				sessions = append(sessions, session)
193
 			}
201
 			}
194
 		}
202
 		}
195
-
196
-		set.Add(client)
197
 	}
203
 	}
198
 
204
 
199
-	return set
205
+	return
200
 }
206
 }
201
 
207
 
202
 // FindAll returns all clients that match the given userhost mask.
208
 // FindAll returns all clients that match the given userhost mask.

+ 5
- 5
irc/commands.go View File

21
 }
21
 }
22
 
22
 
23
 // Run runs this command with the given client/message.
23
 // Run runs this command with the given client/message.
24
-func (cmd *Command) Run(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
24
+func (cmd *Command) Run(server *Server, client *Client, session *Session, msg ircmsg.IrcMessage) bool {
25
 	if !client.registered && !cmd.usablePreReg {
25
 	if !client.registered && !cmd.usablePreReg {
26
 		client.Send(nil, server.name, ERR_NOTREGISTERED, "*", client.t("You need to register before you can use that command"))
26
 		client.Send(nil, server.name, ERR_NOTREGISTERED, "*", client.t("You need to register before you can use that command"))
27
 		return false
27
 		return false
40
 	}
40
 	}
41
 
41
 
42
 	if client.registered {
42
 	if client.registered {
43
-		client.fakelag.Touch()
43
+		session.fakelag.Touch()
44
 	}
44
 	}
45
 
45
 
46
-	rb := NewResponseBuffer(client)
46
+	rb := NewResponseBuffer(session)
47
 	rb.Label = GetLabel(msg)
47
 	rb.Label = GetLabel(msg)
48
 	exiting := cmd.handler(server, client, msg, rb)
48
 	exiting := cmd.handler(server, client, msg, rb)
49
 	rb.Send(true)
49
 	rb.Send(true)
50
 
50
 
51
 	// after each command, see if we can send registration to the client
51
 	// after each command, see if we can send registration to the client
52
 	if !client.registered {
52
 	if !client.registered {
53
-		server.tryRegister(client)
53
+		server.tryRegister(client, session)
54
 	}
54
 	}
55
 
55
 
56
 	// most servers do this only for PING/PONG, but we'll do it for any command:
56
 	// most servers do this only for PING/PONG, but we'll do it for any command:
57
 	if client.registered {
57
 	if client.registered {
58
-		client.idletimer.Touch()
58
+		session.idletimer.Touch()
59
 	}
59
 	}
60
 
60
 
61
 	if !cmd.leaveClientIdle {
61
 	if !cmd.leaveClientIdle {

+ 5
- 1
irc/config.go View File

66
 	} `yaml:"login-throttling"`
66
 	} `yaml:"login-throttling"`
67
 	SkipServerPassword bool                  `yaml:"skip-server-password"`
67
 	SkipServerPassword bool                  `yaml:"skip-server-password"`
68
 	NickReservation    NickReservationConfig `yaml:"nick-reservation"`
68
 	NickReservation    NickReservationConfig `yaml:"nick-reservation"`
69
-	VHosts             VHostConfig
69
+	Bouncer            struct {
70
+		Enabled          bool
71
+		AllowedByDefault bool `yaml:"allowed-by-default"`
72
+	}
73
+	VHosts VHostConfig
70
 }
74
 }
71
 
75
 
72
 // AccountRegistrationConfig controls account registration.
76
 // AccountRegistrationConfig controls account registration.

+ 6
- 6
irc/gateways.go View File

46
 }
46
 }
47
 
47
 
48
 // ApplyProxiedIP applies the given IP to the client.
48
 // ApplyProxiedIP applies the given IP to the client.
49
-func (client *Client) ApplyProxiedIP(proxiedIP string, tls bool) (success bool) {
49
+func (client *Client) ApplyProxiedIP(session *Session, proxiedIP string, tls bool) (success bool) {
50
 	// PROXY and WEBIRC are never accepted from a Tor listener, even if the address itself
50
 	// PROXY and WEBIRC are never accepted from a Tor listener, even if the address itself
51
 	// is whitelisted:
51
 	// is whitelisted:
52
 	if client.isTor {
52
 	if client.isTor {
56
 	// ensure IP is sane
56
 	// ensure IP is sane
57
 	parsedProxiedIP := net.ParseIP(proxiedIP).To16()
57
 	parsedProxiedIP := net.ParseIP(proxiedIP).To16()
58
 	if parsedProxiedIP == nil {
58
 	if parsedProxiedIP == nil {
59
-		client.Quit(fmt.Sprintf(client.t("Proxied IP address is not valid: [%s]"), proxiedIP))
59
+		client.Quit(fmt.Sprintf(client.t("Proxied IP address is not valid: [%s]"), proxiedIP), session)
60
 		return false
60
 		return false
61
 	}
61
 	}
62
 
62
 
63
 	isBanned, banMsg := client.server.checkBans(parsedProxiedIP)
63
 	isBanned, banMsg := client.server.checkBans(parsedProxiedIP)
64
 	if isBanned {
64
 	if isBanned {
65
-		client.Quit(banMsg)
65
+		client.Quit(banMsg, session)
66
 		return false
66
 		return false
67
 	}
67
 	}
68
 
68
 
88
 // PROXY TCP[46] SOURCEIP DESTIP SOURCEPORT DESTPORT\r\n
88
 // PROXY TCP[46] SOURCEIP DESTIP SOURCEPORT DESTPORT\r\n
89
 // unfortunately, an ipv6 SOURCEIP can start with a double colon; in this case,
89
 // unfortunately, an ipv6 SOURCEIP can start with a double colon; in this case,
90
 // the message is invalid IRC and can't be parsed normally, hence the special handling.
90
 // the message is invalid IRC and can't be parsed normally, hence the special handling.
91
-func handleProxyCommand(server *Server, client *Client, line string) (err error) {
91
+func handleProxyCommand(server *Server, client *Client, session *Session, line string) (err error) {
92
 	defer func() {
92
 	defer func() {
93
 		if err != nil {
93
 		if err != nil {
94
-			client.Quit(client.t("Bad or unauthorized PROXY command"))
94
+			client.Quit(client.t("Bad or unauthorized PROXY command"), session)
95
 		}
95
 		}
96
 	}()
96
 	}()
97
 
97
 
102
 
102
 
103
 	if utils.IPInNets(client.realIP, server.Config().Server.proxyAllowedFromNets) {
103
 	if utils.IPInNets(client.realIP, server.Config().Server.proxyAllowedFromNets) {
104
 		// assume PROXY connections are always secure
104
 		// assume PROXY connections are always secure
105
-		if client.ApplyProxiedIP(params[2], true) {
105
+		if client.ApplyProxiedIP(session, params[2], true) {
106
 			return nil
106
 			return nil
107
 		} else {
107
 		} else {
108
 			return errBadProxyLine
108
 			return errBadProxyLine

+ 37
- 6
irc/getters.go View File

62
 	return server.Config().languageManager
62
 	return server.Config().languageManager
63
 }
63
 }
64
 
64
 
65
+func (client *Client) Sessions() (sessions []*Session) {
66
+	client.stateMutex.RLock()
67
+	sessions = make([]*Session, len(client.sessions))
68
+	copy(sessions, client.sessions)
69
+	client.stateMutex.RUnlock()
70
+	return
71
+}
72
+
73
+func (client *Client) AddSession(session *Session) (success bool) {
74
+	client.stateMutex.Lock()
75
+	defer client.stateMutex.Unlock()
76
+
77
+	if len(client.sessions) == 0 {
78
+		return false
79
+	}
80
+	session.client = client
81
+	client.sessions = append(client.sessions, session)
82
+	return true
83
+}
84
+
85
+func (client *Client) removeSession(session *Session) (success bool, length int) {
86
+	if len(client.sessions) == 0 {
87
+		return
88
+	}
89
+	sessions := make([]*Session, 0, len(client.sessions)-1)
90
+	for _, currentSession := range client.sessions {
91
+		if session == currentSession {
92
+			success = true
93
+		} else {
94
+			sessions = append(sessions, currentSession)
95
+		}
96
+	}
97
+	client.sessions = sessions
98
+	length = len(sessions)
99
+	return
100
+}
101
+
65
 func (client *Client) Nick() string {
102
 func (client *Client) Nick() string {
66
 	client.stateMutex.RLock()
103
 	client.stateMutex.RLock()
67
 	defer client.stateMutex.RUnlock()
104
 	defer client.stateMutex.RUnlock()
167
 	client.stateMutex.Unlock()
204
 	client.stateMutex.Unlock()
168
 }
205
 }
169
 
206
 
170
-func (client *Client) Destroyed() bool {
171
-	client.stateMutex.RLock()
172
-	defer client.stateMutex.RUnlock()
173
-	return client.isDestroyed
174
-}
175
-
176
 func (client *Client) Account() string {
207
 func (client *Client) Account() string {
177
 	client.stateMutex.RLock()
208
 	client.stateMutex.RLock()
178
 	defer client.stateMutex.RUnlock()
209
 	defer client.stateMutex.RUnlock()

+ 109
- 76
irc/handlers.go View File

482
 		Mode: modes.Away,
482
 		Mode: modes.Away,
483
 		Op:   op,
483
 		Op:   op,
484
 	}}
484
 	}}
485
-	rb.Add(nil, server.name, "MODE", client.nick, modech.String())
485
+
486
+	details := client.Details()
487
+	modeString := modech.String()
488
+	rb.Add(nil, server.name, "MODE", details.nick, modeString)
486
 
489
 
487
 	// dispatch away-notify
490
 	// dispatch away-notify
488
-	for friend := range client.Friends(caps.AwayNotify) {
491
+	for session := range client.Friends(caps.AwayNotify) {
492
+		if session != rb.session && rb.session.client == client {
493
+			session.Send(nil, server.name, "MODE", details.nick, modeString)
494
+		}
489
 		if isAway {
495
 		if isAway {
490
-			friend.SendFromClient("", client, nil, "AWAY", awayMessage)
496
+			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.account, nil, "AWAY", awayMessage)
491
 		} else {
497
 		} else {
492
-			friend.SendFromClient("", client, nil, "AWAY")
498
+			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.account, nil, "AWAY")
493
 		}
499
 		}
494
 	}
500
 	}
495
 
501
 
527
 	switch subCommand {
533
 	switch subCommand {
528
 	case "LS":
534
 	case "LS":
529
 		if !client.registered {
535
 		if !client.registered {
530
-			client.capState = caps.NegotiatingState
536
+			rb.session.capState = caps.NegotiatingState
531
 		}
537
 		}
532
 		if len(msg.Params) > 1 && msg.Params[1] == "302" {
538
 		if len(msg.Params) > 1 && msg.Params[1] == "302" {
533
-			client.capVersion = 302
539
+			rb.session.capVersion = 302
534
 		}
540
 		}
535
 		// weechat 1.4 has a bug here where it won't accept the CAP reply unless it contains
541
 		// weechat 1.4 has a bug here where it won't accept the CAP reply unless it contains
536
 		// the server.name source... otherwise it doesn't respond to the CAP message with
542
 		// the server.name source... otherwise it doesn't respond to the CAP message with
537
 		// anything and just hangs on connection.
543
 		// anything and just hangs on connection.
538
 		//TODO(dan): limit number of caps and send it multiline in 3.2 style as appropriate.
544
 		//TODO(dan): limit number of caps and send it multiline in 3.2 style as appropriate.
539
-		rb.Add(nil, server.name, "CAP", client.nick, subCommand, SupportedCapabilities.String(client.capVersion, CapValues))
545
+		rb.Add(nil, server.name, "CAP", client.nick, subCommand, SupportedCapabilities.String(rb.session.capVersion, CapValues))
540
 
546
 
541
 	case "LIST":
547
 	case "LIST":
542
-		rb.Add(nil, server.name, "CAP", client.nick, subCommand, client.capabilities.String(caps.Cap301, CapValues)) // values not sent on LIST so force 3.1
548
+		rb.Add(nil, server.name, "CAP", client.nick, subCommand, rb.session.capabilities.String(caps.Cap301, CapValues)) // values not sent on LIST so force 3.1
543
 
549
 
544
 	case "REQ":
550
 	case "REQ":
545
 		if !client.registered {
551
 		if !client.registered {
546
-			client.capState = caps.NegotiatingState
552
+			rb.session.capState = caps.NegotiatingState
547
 		}
553
 		}
548
 
554
 
549
 		// make sure all capabilities actually exist
555
 		// make sure all capabilities actually exist
551
 			rb.Add(nil, server.name, "CAP", client.nick, "NAK", capString)
557
 			rb.Add(nil, server.name, "CAP", client.nick, "NAK", capString)
552
 			return false
558
 			return false
553
 		}
559
 		}
554
-		client.capabilities.Union(toAdd)
555
-		client.capabilities.Subtract(toRemove)
560
+		rb.session.capabilities.Union(toAdd)
561
+		rb.session.capabilities.Subtract(toRemove)
556
 		rb.Add(nil, server.name, "CAP", client.nick, "ACK", capString)
562
 		rb.Add(nil, server.name, "CAP", client.nick, "ACK", capString)
557
 
563
 
558
 		// if this is the first time the client is requesting a resume token,
564
 		// if this is the first time the client is requesting a resume token,
564
 			}
570
 			}
565
 		}
571
 		}
566
 
572
 
573
+		// update maxlenrest, just in case they altered the maxline cap
574
+		rb.session.SetMaxlenRest()
575
+
567
 	case "END":
576
 	case "END":
568
 		if !client.registered {
577
 		if !client.registered {
569
-			client.capState = caps.NegotiatedState
578
+			rb.session.capState = caps.NegotiatedState
570
 		}
579
 		}
571
 
580
 
572
 	default:
581
 	default:
600
 		if success && len(items) > 0 {
609
 		if success && len(items) > 0 {
601
 			return
610
 			return
602
 		}
611
 		}
603
-		newRb := NewResponseBuffer(client)
612
+		newRb := NewResponseBuffer(rb.session)
604
 		newRb.Label = rb.Label // same label, new batch
613
 		newRb.Label = rb.Label // same label, new batch
605
 		// TODO: send `WARN CHATHISTORY MAX_MESSAGES_EXCEEDED` when appropriate
614
 		// TODO: send `WARN CHATHISTORY MAX_MESSAGES_EXCEEDED` when appropriate
606
 		if hist == nil {
615
 		if hist == nil {
1006
 
1015
 
1007
 		for _, mcl := range clientsToKill {
1016
 		for _, mcl := range clientsToKill {
1008
 			mcl.exitedSnomaskSent = true
1017
 			mcl.exitedSnomaskSent = true
1009
-			mcl.Quit(fmt.Sprintf(mcl.t("You have been banned from this server (%s)"), reason))
1018
+			mcl.Quit(fmt.Sprintf(mcl.t("You have been banned from this server (%s)"), reason), nil)
1010
 			if mcl == client {
1019
 			if mcl == client {
1011
 				killClient = true
1020
 				killClient = true
1012
 			} else {
1021
 			} else {
1013
 				// if mcl == client, we kill them below
1022
 				// if mcl == client, we kill them below
1014
-				mcl.destroy(false)
1023
+				mcl.destroy(false, nil)
1015
 			}
1024
 			}
1016
 		}
1025
 		}
1017
 
1026
 
1240
 				return false
1249
 				return false
1241
 			}
1250
 			}
1242
 			channelString = msg.Params[1]
1251
 			channelString = msg.Params[1]
1243
-			rb = NewResponseBuffer(target)
1244
 		}
1252
 		}
1245
 	}
1253
 	}
1246
 
1254
 
1248
 	for _, chname := range channels {
1256
 	for _, chname := range channels {
1249
 		server.channels.Join(target, chname, "", true, rb)
1257
 		server.channels.Join(target, chname, "", true, rb)
1250
 	}
1258
 	}
1251
-	if client != target {
1252
-		rb.Send(false)
1253
-	}
1254
 	return false
1259
 	return false
1255
 }
1260
 }
1256
 
1261
 
1321
 	server.snomasks.Send(sno.LocalKills, fmt.Sprintf(ircfmt.Unescape("%s$r was killed by %s $c[grey][$r%s$c[grey]]"), target.nick, client.nick, comment))
1326
 	server.snomasks.Send(sno.LocalKills, fmt.Sprintf(ircfmt.Unescape("%s$r was killed by %s $c[grey][$r%s$c[grey]]"), target.nick, client.nick, comment))
1322
 	target.exitedSnomaskSent = true
1327
 	target.exitedSnomaskSent = true
1323
 
1328
 
1324
-	target.Quit(quitMsg)
1325
-	target.destroy(false)
1329
+	target.Quit(quitMsg, nil)
1330
+	target.destroy(false, nil)
1326
 	return false
1331
 	return false
1327
 }
1332
 }
1328
 
1333
 
1447
 
1452
 
1448
 		for _, mcl := range clientsToKill {
1453
 		for _, mcl := range clientsToKill {
1449
 			mcl.exitedSnomaskSent = true
1454
 			mcl.exitedSnomaskSent = true
1450
-			mcl.Quit(fmt.Sprintf(mcl.t("You have been banned from this server (%s)"), reason))
1455
+			mcl.Quit(fmt.Sprintf(mcl.t("You have been banned from this server (%s)"), reason), nil)
1451
 			if mcl == client {
1456
 			if mcl == client {
1452
 				killClient = true
1457
 				killClient = true
1453
 			} else {
1458
 			} else {
1454
 				// if mcl == client, we kill them below
1459
 				// if mcl == client, we kill them below
1455
-				mcl.destroy(false)
1460
+				mcl.destroy(false, nil)
1456
 			}
1461
 			}
1457
 		}
1462
 		}
1458
 
1463
 
1660
 	}
1665
 	}
1661
 
1666
 
1662
 	// send out changes
1667
 	// send out changes
1668
+	prefix := client.NickMaskString()
1663
 	if len(applied) > 0 {
1669
 	if len(applied) > 0 {
1664
 		//TODO(dan): we should change the name of String and make it return a slice here
1670
 		//TODO(dan): we should change the name of String and make it return a slice here
1665
 		args := append([]string{channel.name}, strings.Split(applied.String(), " ")...)
1671
 		args := append([]string{channel.name}, strings.Split(applied.String(), " ")...)
1666
 		for _, member := range channel.Members() {
1672
 		for _, member := range channel.Members() {
1667
 			if member == client {
1673
 			if member == client {
1668
-				rb.Add(nil, client.nickMaskString, "MODE", args...)
1674
+				rb.Add(nil, prefix, "MODE", args...)
1675
+				for _, session := range client.Sessions() {
1676
+					if session != rb.session {
1677
+						session.Send(nil, prefix, "MODE", args...)
1678
+					}
1679
+				}
1669
 			} else {
1680
 			} else {
1670
-				member.Send(nil, client.nickMaskString, "MODE", args...)
1681
+				member.Send(nil, prefix, "MODE", args...)
1671
 			}
1682
 			}
1672
 		}
1683
 		}
1673
 	} else {
1684
 	} else {
1674
 		args := append([]string{client.nick, channel.name}, channel.modeStrings(client)...)
1685
 		args := append([]string{client.nick, channel.name}, channel.modeStrings(client)...)
1675
-		rb.Add(nil, client.nickMaskString, RPL_CHANNELMODEIS, args...)
1686
+		rb.Add(nil, prefix, RPL_CHANNELMODEIS, args...)
1676
 		rb.Add(nil, client.nickMaskString, RPL_CHANNELCREATED, client.nick, channel.name, strconv.FormatInt(channel.createdTime.Unix(), 10))
1687
 		rb.Add(nil, client.nickMaskString, RPL_CHANNELCREATED, client.nick, channel.name, strconv.FormatInt(channel.createdTime.Unix(), 10))
1677
 	}
1688
 	}
1678
 	return false
1689
 	return false
1913
 // NICK <nickname>
1924
 // NICK <nickname>
1914
 func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1925
 func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1915
 	if client.registered {
1926
 	if client.registered {
1916
-		performNickChange(server, client, client, msg.Params[0], rb)
1927
+		performNickChange(server, client, client, nil, msg.Params[0], rb)
1917
 	} else {
1928
 	} else {
1918
 		client.preregNick = msg.Params[0]
1929
 		client.preregNick = msg.Params[0]
1919
 	}
1930
 	}
1953
 
1964
 
1954
 	for i, targetString := range targets {
1965
 	for i, targetString := range targets {
1955
 		// each target gets distinct msgids
1966
 		// each target gets distinct msgids
1956
-		splitMsg := utils.MakeSplitMessage(message, !client.capabilities.Has(caps.MaxLine))
1967
+		splitMsg := utils.MakeSplitMessage(message, !rb.session.capabilities.Has(caps.MaxLine))
1957
 		now := time.Now().UTC()
1968
 		now := time.Now().UTC()
1958
 
1969
 
1959
 		// max of four targets per privmsg
1970
 		// max of four targets per privmsg
1992
 			}
2003
 			}
1993
 			tnick := user.Nick()
2004
 			tnick := user.Nick()
1994
 
2005
 
1995
-			if histType == history.Tagmsg && !user.capabilities.Has(caps.MessageTags) {
1996
-				continue // nothing to do
1997
-			}
1998
-
1999
 			nickMaskString := client.NickMaskString()
2006
 			nickMaskString := client.NickMaskString()
2000
 			accountName := client.AccountName()
2007
 			accountName := client.AccountName()
2001
 			// restrict messages appropriately when +R is set
2008
 			// restrict messages appropriately when +R is set
2003
 			allowedPlusR := !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount()
2010
 			allowedPlusR := !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount()
2004
 			allowedTor := !user.isTor || !isRestrictedCTCPMessage(message)
2011
 			allowedTor := !user.isTor || !isRestrictedCTCPMessage(message)
2005
 			if allowedPlusR && allowedTor {
2012
 			if allowedPlusR && allowedTor {
2006
-				if histType == history.Tagmsg {
2007
-					user.sendFromClientInternal(false, now, splitMsg.Msgid, nickMaskString, accountName, clientOnlyTags, msg.Command, tnick)
2008
-				} else {
2009
-					user.SendSplitMsgFromClient(client, clientOnlyTags, msg.Command, tnick, splitMsg)
2013
+				for _, session := range user.Sessions() {
2014
+					if histType == history.Tagmsg {
2015
+						// don't send TAGMSG at all if they don't have the tags cap
2016
+						if session.capabilities.Has(caps.MessageTags) {
2017
+							session.sendFromClientInternal(false, now, splitMsg.Msgid, nickMaskString, accountName, clientOnlyTags, msg.Command, tnick)
2018
+						}
2019
+					} else {
2020
+						session.sendSplitMsgFromClientInternal(false, now, nickMaskString, accountName, clientOnlyTags, msg.Command, tnick, splitMsg)
2021
+					}
2010
 				}
2022
 				}
2011
 			}
2023
 			}
2012
-			if client.capabilities.Has(caps.EchoMessage) {
2013
-				if histType == history.Tagmsg && client.capabilities.Has(caps.MessageTags) {
2024
+			// an echo-message may need to be included in the response:
2025
+			if rb.session.capabilities.Has(caps.EchoMessage) {
2026
+				if histType == history.Tagmsg && rb.session.capabilities.Has(caps.MessageTags) {
2014
 					rb.AddFromClient(splitMsg.Msgid, nickMaskString, accountName, clientOnlyTags, msg.Command, tnick)
2027
 					rb.AddFromClient(splitMsg.Msgid, nickMaskString, accountName, clientOnlyTags, msg.Command, tnick)
2015
 				} else {
2028
 				} else {
2016
 					rb.AddSplitMessageFromClient(nickMaskString, accountName, clientOnlyTags, msg.Command, tnick, splitMsg)
2029
 					rb.AddSplitMessageFromClient(nickMaskString, accountName, clientOnlyTags, msg.Command, tnick, splitMsg)
2017
 				}
2030
 				}
2018
 			}
2031
 			}
2032
+			// an echo-message may need to go out to other client sessions:
2033
+			for _, session := range client.Sessions() {
2034
+				if session == rb.session || !rb.session.capabilities.SelfMessagesEnabled() {
2035
+					continue
2036
+				}
2037
+				if histType == history.Tagmsg && rb.session.capabilities.Has(caps.MessageTags) {
2038
+					session.sendFromClientInternal(false, now, splitMsg.Msgid, nickMaskString, accountName, clientOnlyTags, msg.Command, tnick)
2039
+				} else {
2040
+					session.sendSplitMsgFromClientInternal(false, now, nickMaskString, accountName, clientOnlyTags, msg.Command, tnick, splitMsg)
2041
+				}
2042
+			}
2019
 			if histType != history.Notice && user.HasMode(modes.Away) {
2043
 			if histType != history.Notice && user.HasMode(modes.Away) {
2020
 				//TODO(dan): possibly implement cooldown of away notifications to users
2044
 				//TODO(dan): possibly implement cooldown of away notifications to users
2021
 				rb.Add(nil, server.name, RPL_AWAY, cnick, tnick, user.AwayMessage())
2045
 				rb.Add(nil, server.name, RPL_AWAY, cnick, tnick, user.AwayMessage())
2084
 	}
2108
 	}
2085
 	if !authorized {
2109
 	if !authorized {
2086
 		rb.Add(nil, server.name, ERR_PASSWDMISMATCH, client.Nick(), client.t("Password incorrect"))
2110
 		rb.Add(nil, server.name, ERR_PASSWDMISMATCH, client.Nick(), client.t("Password incorrect"))
2087
-		client.Quit(client.t("Password incorrect"))
2111
+		client.Quit(client.t("Password incorrect"), rb.session)
2088
 		return true
2112
 		return true
2089
 	}
2113
 	}
2090
 
2114
 
2109
 	server.snomasks.Send(sno.LocalOpers, fmt.Sprintf(ircfmt.Unescape("Client opered up $c[grey][$r%s$c[grey], $r%s$c[grey]]"), client.nickMaskString, oper.Name))
2133
 	server.snomasks.Send(sno.LocalOpers, fmt.Sprintf(ircfmt.Unescape("Client opered up $c[grey][$r%s$c[grey], $r%s$c[grey]]"), client.nickMaskString, oper.Name))
2110
 
2134
 
2111
 	// client may now be unthrottled by the fakelag system
2135
 	// client may now be unthrottled by the fakelag system
2112
-	client.resetFakelag()
2136
+	for _, session := range client.Sessions() {
2137
+		session.resetFakelag()
2138
+	}
2113
 
2139
 
2114
 	return false
2140
 	return false
2115
 }
2141
 }
2148
 	password := []byte(msg.Params[0])
2174
 	password := []byte(msg.Params[0])
2149
 	if bcrypt.CompareHashAndPassword(serverPassword, password) != nil {
2175
 	if bcrypt.CompareHashAndPassword(serverPassword, password) != nil {
2150
 		rb.Add(nil, server.name, ERR_PASSWDMISMATCH, client.nick, client.t("Password incorrect"))
2176
 		rb.Add(nil, server.name, ERR_PASSWDMISMATCH, client.nick, client.t("Password incorrect"))
2151
-		client.Quit(client.t("Password incorrect"))
2177
+		client.Quit(client.t("Password incorrect"), rb.session)
2152
 		return true
2178
 		return true
2153
 	}
2179
 	}
2154
 
2180
 
2180
 	if len(msg.Params) > 0 {
2206
 	if len(msg.Params) > 0 {
2181
 		reason += ": " + msg.Params[0]
2207
 		reason += ": " + msg.Params[0]
2182
 	}
2208
 	}
2183
-	client.Quit(reason)
2209
+	client.Quit(reason, rb.session)
2184
 	return true
2210
 	return true
2185
 }
2211
 }
2186
 
2212
 
2242
 	// send RENAME messages
2268
 	// send RENAME messages
2243
 	clientPrefix := client.NickMaskString()
2269
 	clientPrefix := client.NickMaskString()
2244
 	for _, mcl := range channel.Members() {
2270
 	for _, mcl := range channel.Members() {
2245
-		targetRb := rb
2246
-		targetPrefix := clientPrefix
2247
-		if mcl != client {
2248
-			targetRb = NewResponseBuffer(mcl)
2249
-			targetPrefix = mcl.NickMaskString()
2250
-		}
2251
-		if mcl.capabilities.Has(caps.Rename) {
2252
-			if reason != "" {
2253
-				targetRb.Add(nil, clientPrefix, "RENAME", oldName, newName, reason)
2254
-			} else {
2255
-				targetRb.Add(nil, clientPrefix, "RENAME", oldName, newName)
2271
+		for _, mSession := range mcl.Sessions() {
2272
+			targetRb := rb
2273
+			targetPrefix := clientPrefix
2274
+			if mSession != rb.session {
2275
+				targetRb = NewResponseBuffer(mSession)
2276
+				targetPrefix = mcl.NickMaskString()
2256
 			}
2277
 			}
2257
-		} else {
2258
-			if reason != "" {
2259
-				targetRb.Add(nil, targetPrefix, "PART", oldName, fmt.Sprintf(mcl.t("Channel renamed: %s"), reason))
2278
+			if mSession.capabilities.Has(caps.Rename) {
2279
+				if reason != "" {
2280
+					targetRb.Add(nil, clientPrefix, "RENAME", oldName, newName, reason)
2281
+				} else {
2282
+					targetRb.Add(nil, clientPrefix, "RENAME", oldName, newName)
2283
+				}
2260
 			} else {
2284
 			} else {
2261
-				targetRb.Add(nil, targetPrefix, "PART", oldName, fmt.Sprintf(mcl.t("Channel renamed")))
2285
+				if reason != "" {
2286
+					targetRb.Add(nil, targetPrefix, "PART", oldName, fmt.Sprintf(mcl.t("Channel renamed: %s"), reason))
2287
+				} else {
2288
+					targetRb.Add(nil, targetPrefix, "PART", oldName, fmt.Sprintf(mcl.t("Channel renamed")))
2289
+				}
2290
+				if mSession.capabilities.Has(caps.ExtendedJoin) {
2291
+					targetRb.Add(nil, targetPrefix, "JOIN", newName, mcl.AccountName(), mcl.Realname())
2292
+				} else {
2293
+					targetRb.Add(nil, targetPrefix, "JOIN", newName)
2294
+				}
2295
+				channel.SendTopic(mcl, targetRb, false)
2296
+				channel.Names(mcl, targetRb)
2262
 			}
2297
 			}
2263
-			if mcl.capabilities.Has(caps.ExtendedJoin) {
2264
-				targetRb.Add(nil, targetPrefix, "JOIN", newName, mcl.AccountName(), mcl.Realname())
2265
-			} else {
2266
-				targetRb.Add(nil, targetPrefix, "JOIN", newName)
2298
+			if mcl != client {
2299
+				targetRb.Send(false)
2267
 			}
2300
 			}
2268
-			channel.SendTopic(mcl, targetRb, false)
2269
-			channel.Names(mcl, targetRb)
2270
-		}
2271
-		if mcl != client {
2272
-			targetRb.Send(false)
2273
 		}
2301
 		}
2274
 	}
2302
 	}
2275
 
2303
 
2311
 		rb.Add(nil, server.name, ERR_NOSUCHNICK, client.nick, msg.Params[0], client.t("No such nick"))
2339
 		rb.Add(nil, server.name, ERR_NOSUCHNICK, client.nick, msg.Params[0], client.t("No such nick"))
2312
 		return false
2340
 		return false
2313
 	}
2341
 	}
2314
-	performNickChange(server, client, target, msg.Params[1], rb)
2342
+	performNickChange(server, client, target, nil, msg.Params[1], rb)
2315
 	return false
2343
 	return false
2316
 }
2344
 }
2317
 
2345
 
2334
 	client.realname = realname
2362
 	client.realname = realname
2335
 	client.stateMutex.Unlock()
2363
 	client.stateMutex.Unlock()
2336
 
2364
 
2365
+	details := client.Details()
2366
+
2337
 	// alert friends
2367
 	// alert friends
2338
-	for friend := range client.Friends(caps.SetName) {
2339
-		friend.SendFromClient("", client, nil, "SETNAME", realname)
2368
+	now := time.Now().UTC()
2369
+	for session := range client.Friends(caps.SetName) {
2370
+		session.sendFromClientInternal(false, now, "", details.nickMask, details.account, nil, "SETNAME", details.realname)
2340
 	}
2371
 	}
2341
 
2372
 
2342
 	return false
2373
 	return false
2519
 			lkey := strings.ToLower(key)
2550
 			lkey := strings.ToLower(key)
2520
 			if lkey == "tls" || lkey == "secure" {
2551
 			if lkey == "tls" || lkey == "secure" {
2521
 				// only accept "tls" flag if the gateway's connection to us is secure as well
2552
 				// only accept "tls" flag if the gateway's connection to us is secure as well
2522
-				if client.HasMode(modes.TLS) || utils.AddrIsLocal(client.socket.conn.RemoteAddr()) {
2553
+				if client.HasMode(modes.TLS) || client.realIP.IsLoopback() {
2523
 					secure = true
2554
 					secure = true
2524
 				}
2555
 				}
2525
 			}
2556
 			}
2543
 			if strings.HasPrefix(proxiedIP, "[") && strings.HasSuffix(proxiedIP, "]") {
2574
 			if strings.HasPrefix(proxiedIP, "[") && strings.HasSuffix(proxiedIP, "]") {
2544
 				proxiedIP = proxiedIP[1 : len(proxiedIP)-1]
2575
 				proxiedIP = proxiedIP[1 : len(proxiedIP)-1]
2545
 			}
2576
 			}
2546
-			return !client.ApplyProxiedIP(proxiedIP, secure)
2577
+			return !client.ApplyProxiedIP(rb.session, proxiedIP, secure)
2547
 		}
2578
 		}
2548
 	}
2579
 	}
2549
 
2580
 
2550
-	client.Quit(client.t("WEBIRC command is not usable from your address or incorrect password given"))
2581
+	client.Quit(client.t("WEBIRC command is not usable from your address or incorrect password given"), rb.session)
2551
 	return true
2582
 	return true
2552
 }
2583
 }
2553
 
2584
 
2568
 		mask = casefoldedMask
2599
 		mask = casefoldedMask
2569
 	}
2600
 	}
2570
 
2601
 
2571
-	friends := client.Friends()
2572
-
2573
 	//TODO(dan): is this used and would I put this param in the Modern doc?
2602
 	//TODO(dan): is this used and would I put this param in the Modern doc?
2574
 	// if not, can we remove it?
2603
 	// if not, can we remove it?
2575
 	//var operatorOnly bool
2604
 	//var operatorOnly bool
2581
 		// TODO implement wildcard matching
2610
 		// TODO implement wildcard matching
2582
 		//TODO(dan): ^ only for opers
2611
 		//TODO(dan): ^ only for opers
2583
 		channel := server.channels.Get(mask)
2612
 		channel := server.channels.Get(mask)
2584
-		if channel != nil {
2585
-			whoChannel(client, channel, friends, rb)
2613
+		if channel != nil && channel.hasClient(client) {
2614
+			for _, member := range channel.Members() {
2615
+				if !member.HasMode(modes.Invisible) {
2616
+					client.rplWhoReply(channel, member, rb)
2617
+				}
2618
+			}
2586
 		}
2619
 		}
2587
 	} else {
2620
 	} else {
2588
 		for mclient := range server.clients.FindAll(mask) {
2621
 		for mclient := range server.clients.FindAll(mask) {

+ 14
- 9
irc/idletimer.go View File

45
 
45
 
46
 	// immutable after construction
46
 	// immutable after construction
47
 	registerTimeout time.Duration
47
 	registerTimeout time.Duration
48
-	client          *Client
48
+	session         *Session
49
 
49
 
50
 	// mutable
50
 	// mutable
51
 	idleTimeout time.Duration
51
 	idleTimeout time.Duration
56
 
56
 
57
 // Initialize sets up an IdleTimer and starts counting idle time;
57
 // Initialize sets up an IdleTimer and starts counting idle time;
58
 // if there is no activity from the client, it will eventually be stopped.
58
 // if there is no activity from the client, it will eventually be stopped.
59
-func (it *IdleTimer) Initialize(client *Client) {
60
-	it.client = client
59
+func (it *IdleTimer) Initialize(session *Session) {
60
+	it.session = session
61
 	it.registerTimeout = RegisterTimeout
61
 	it.registerTimeout = RegisterTimeout
62
 	it.idleTimeout, it.quitTimeout = it.recomputeDurations()
62
 	it.idleTimeout, it.quitTimeout = it.recomputeDurations()
63
+	registered := session.client.Registered()
63
 
64
 
64
 	it.Lock()
65
 	it.Lock()
65
 	defer it.Unlock()
66
 	defer it.Unlock()
66
-	it.state = TimerUnregistered
67
+	if registered {
68
+		it.state = TimerActive
69
+	} else {
70
+		it.state = TimerUnregistered
71
+	}
67
 	it.resetTimeout()
72
 	it.resetTimeout()
68
 }
73
 }
69
 
74
 
72
 	totalTimeout := DefaultTotalTimeout
77
 	totalTimeout := DefaultTotalTimeout
73
 	// if they have the resume cap, wait longer before pinging them out
78
 	// if they have the resume cap, wait longer before pinging them out
74
 	// to give them a chance to resume their connection
79
 	// to give them a chance to resume their connection
75
-	if it.client.capabilities.Has(caps.Resume) {
80
+	if it.session.capabilities.Has(caps.Resume) {
76
 		totalTimeout = ResumeableTotalTimeout
81
 		totalTimeout = ResumeableTotalTimeout
77
 	}
82
 	}
78
 
83
 
79
 	idleTimeout = DefaultIdleTimeout
84
 	idleTimeout = DefaultIdleTimeout
80
-	if it.client.isTor {
85
+	if it.session.client.isTor {
81
 		idleTimeout = TorIdleTimeout
86
 		idleTimeout = TorIdleTimeout
82
 	}
87
 	}
83
 
88
 
118
 	}()
123
 	}()
119
 
124
 
120
 	if previousState == TimerActive {
125
 	if previousState == TimerActive {
121
-		it.client.Ping()
126
+		it.session.Ping()
122
 	} else {
127
 	} else {
123
-		it.client.Quit(it.quitMessage(previousState))
124
-		it.client.destroy(false)
128
+		it.session.client.Quit(it.quitMessage(previousState), it.session)
129
+		it.session.client.destroy(false, it.session)
125
 	}
130
 	}
126
 }
131
 }
127
 
132
 

+ 15
- 9
irc/nickname.go View File

23
 )
23
 )
24
 
24
 
25
 // returns whether the change succeeded or failed
25
 // returns whether the change succeeded or failed
26
-func performNickChange(server *Server, client *Client, target *Client, newnick string, rb *ResponseBuffer) bool {
26
+func performNickChange(server *Server, client *Client, target *Client, session *Session, newnick string, rb *ResponseBuffer) bool {
27
 	nickname := strings.TrimSpace(newnick)
27
 	nickname := strings.TrimSpace(newnick)
28
 	cfnick, err := CasefoldName(nickname)
28
 	cfnick, err := CasefoldName(nickname)
29
 	currentNick := client.Nick()
29
 	currentNick := client.Nick()
44
 
44
 
45
 	hadNick := target.HasNick()
45
 	hadNick := target.HasNick()
46
 	origNickMask := target.NickMaskString()
46
 	origNickMask := target.NickMaskString()
47
-	whowas := client.WhoWas()
48
-	err = client.server.clients.SetNick(target, nickname)
47
+	whowas := target.WhoWas()
48
+	err = client.server.clients.SetNick(target, session, nickname)
49
 	if err == errNicknameInUse {
49
 	if err == errNicknameInUse {
50
 		rb.Add(nil, server.name, ERR_NICKNAMEINUSE, currentNick, nickname, client.t("Nickname is already in use"))
50
 		rb.Add(nil, server.name, ERR_NICKNAMEINUSE, currentNick, nickname, client.t("Nickname is already in use"))
51
 		return false
51
 		return false
57
 		return false
57
 		return false
58
 	}
58
 	}
59
 
59
 
60
-	client.nickTimer.Touch()
60
+	target.nickTimer.Touch()
61
 
61
 
62
 	client.server.logger.Debug("nick", fmt.Sprintf("%s changed nickname to %s [%s]", origNickMask, nickname, cfnick))
62
 	client.server.logger.Debug("nick", fmt.Sprintf("%s changed nickname to %s [%s]", origNickMask, nickname, cfnick))
63
 	if hadNick {
63
 	if hadNick {
64
 		target.server.snomasks.Send(sno.LocalNicks, fmt.Sprintf(ircfmt.Unescape("$%s$r changed nickname to %s"), whowas.nick, nickname))
64
 		target.server.snomasks.Send(sno.LocalNicks, fmt.Sprintf(ircfmt.Unescape("$%s$r changed nickname to %s"), whowas.nick, nickname))
65
 		target.server.whoWas.Append(whowas)
65
 		target.server.whoWas.Append(whowas)
66
 		rb.Add(nil, origNickMask, "NICK", nickname)
66
 		rb.Add(nil, origNickMask, "NICK", nickname)
67
-		for friend := range target.Friends() {
68
-			if friend != client {
69
-				friend.Send(nil, origNickMask, "NICK", nickname)
67
+		for session := range target.Friends() {
68
+			if session != rb.session {
69
+				session.Send(nil, origNickMask, "NICK", nickname)
70
 			}
70
 			}
71
 		}
71
 		}
72
 	}
72
 	}
86
 	buf := make([]byte, 8)
86
 	buf := make([]byte, 8)
87
 	rand.Read(buf)
87
 	rand.Read(buf)
88
 	nick := fmt.Sprintf("%s%s", prefix, hex.EncodeToString(buf))
88
 	nick := fmt.Sprintf("%s%s", prefix, hex.EncodeToString(buf))
89
-	rb := NewResponseBuffer(client)
90
-	performNickChange(server, client, client, nick, rb)
89
+	sessions := client.Sessions()
90
+	if len(sessions) == 0 {
91
+		return
92
+	}
93
+	// XXX arbitrarily pick the first session to receive error messages;
94
+	// all other sessions receive a `NICK` line same as a friend would
95
+	rb := NewResponseBuffer(sessions[0])
96
+	performNickChange(server, client, client, nil, nick, rb)
91
 	rb.Send(false)
97
 	rb.Send(false)
92
 	// technically performNickChange can fail to change the nick,
98
 	// technically performNickChange can fail to change the nick,
93
 	// but if they're still delinquent, the timer will get them later
99
 	// but if they're still delinquent, the timer will get them later

+ 2
- 2
irc/nickserv.go View File

229
 		return
229
 		return
230
 	}
230
 	}
231
 
231
 
232
-	ghost.Quit(fmt.Sprintf(ghost.t("GHOSTed by %s"), client.Nick()))
233
-	ghost.destroy(false)
232
+	ghost.Quit(fmt.Sprintf(ghost.t("GHOSTed by %s"), client.Nick()), nil)
233
+	ghost.destroy(false, nil)
234
 }
234
 }
235
 
235
 
236
 func nsGroupHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
236
 func nsGroupHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {

+ 13
- 11
irc/responsebuffer.go View File

25
 type ResponseBuffer struct {
25
 type ResponseBuffer struct {
26
 	Label     string
26
 	Label     string
27
 	batchID   string
27
 	batchID   string
28
-	target    *Client
29
 	messages  []ircmsg.IrcMessage
28
 	messages  []ircmsg.IrcMessage
30
 	finalized bool
29
 	finalized bool
30
+	target    *Client
31
+	session   *Session
31
 }
32
 }
32
 
33
 
33
 // GetLabel returns the label from the given message.
34
 // GetLabel returns the label from the given message.
37
 }
38
 }
38
 
39
 
39
 // NewResponseBuffer returns a new ResponseBuffer.
40
 // NewResponseBuffer returns a new ResponseBuffer.
40
-func NewResponseBuffer(target *Client) *ResponseBuffer {
41
+func NewResponseBuffer(session *Session) *ResponseBuffer {
41
 	return &ResponseBuffer{
42
 	return &ResponseBuffer{
42
-		target: target,
43
+		session: session,
44
+		target:  session.client,
43
 	}
45
 	}
44
 }
46
 }
45
 
47
 
66
 	msg.UpdateTags(tags)
68
 	msg.UpdateTags(tags)
67
 
69
 
68
 	// attach account-tag
70
 	// attach account-tag
69
-	if rb.target.capabilities.Has(caps.AccountTag) && fromAccount != "*" {
71
+	if rb.session.capabilities.Has(caps.AccountTag) && fromAccount != "*" {
70
 		msg.SetTag("account", fromAccount)
72
 		msg.SetTag("account", fromAccount)
71
 	}
73
 	}
72
 	// attach message-id
74
 	// attach message-id
73
-	if len(msgid) > 0 && rb.target.capabilities.Has(caps.MessageTags) {
75
+	if len(msgid) > 0 && rb.session.capabilities.Has(caps.MessageTags) {
74
 		msg.SetTag("draft/msgid", msgid)
76
 		msg.SetTag("draft/msgid", msgid)
75
 	}
77
 	}
76
 
78
 
79
 
81
 
80
 // AddSplitMessageFromClient adds a new split message from a specific client to our queue.
82
 // AddSplitMessageFromClient adds a new split message from a specific client to our queue.
81
 func (rb *ResponseBuffer) AddSplitMessageFromClient(fromNickMask string, fromAccount string, tags map[string]string, command string, target string, message utils.SplitMessage) {
83
 func (rb *ResponseBuffer) AddSplitMessageFromClient(fromNickMask string, fromAccount string, tags map[string]string, command string, target string, message utils.SplitMessage) {
82
-	if rb.target.capabilities.Has(caps.MaxLine) || message.Wrapped == nil {
84
+	if rb.session.capabilities.Has(caps.MaxLine) || message.Wrapped == nil {
83
 		rb.AddFromClient(message.Msgid, fromNickMask, fromAccount, tags, command, target, message.Message)
85
 		rb.AddFromClient(message.Msgid, fromNickMask, fromAccount, tags, command, target, message.Message)
84
 	} else {
86
 	} else {
85
 		for _, messagePair := range message.Wrapped {
87
 		for _, messagePair := range message.Wrapped {
110
 	if rb.Label != "" {
112
 	if rb.Label != "" {
111
 		message.SetTag(caps.LabelTagName, rb.Label)
113
 		message.SetTag(caps.LabelTagName, rb.Label)
112
 	}
114
 	}
113
-	rb.target.SendRawMessage(message, blocking)
115
+	rb.session.SendRawMessage(message, blocking)
114
 }
116
 }
115
 
117
 
116
 func (rb *ResponseBuffer) sendBatchEnd(blocking bool) {
118
 func (rb *ResponseBuffer) sendBatchEnd(blocking bool) {
120
 	}
122
 	}
121
 
123
 
122
 	message := ircmsg.MakeMessage(nil, rb.target.server.name, "BATCH", "-"+rb.batchID)
124
 	message := ircmsg.MakeMessage(nil, rb.target.server.name, "BATCH", "-"+rb.batchID)
123
-	rb.target.SendRawMessage(message, blocking)
125
+	rb.session.SendRawMessage(message, blocking)
124
 }
126
 }
125
 
127
 
126
 // Send sends all messages in the buffer to the client.
128
 // Send sends all messages in the buffer to the client.
146
 		return nil
148
 		return nil
147
 	}
149
 	}
148
 
150
 
149
-	useLabel := rb.target.capabilities.Has(caps.LabeledResponse) && rb.Label != ""
151
+	useLabel := rb.session.capabilities.Has(caps.LabeledResponse) && rb.Label != ""
150
 	// use a batch if we have a label, and we either currently have 0 or 2+ messages,
152
 	// use a batch if we have a label, and we either currently have 0 or 2+ messages,
151
 	// or we are doing a Flush() and we have to assume that there will be more messages
153
 	// or we are doing a Flush() and we have to assume that there will be more messages
152
 	// in the future.
154
 	// in the future.
162
 	// send each message out
164
 	// send each message out
163
 	for _, message := range rb.messages {
165
 	for _, message := range rb.messages {
164
 		// attach server-time if needed
166
 		// attach server-time if needed
165
-		if rb.target.capabilities.Has(caps.ServerTime) && !message.HasTag("time") {
167
+		if rb.session.capabilities.Has(caps.ServerTime) && !message.HasTag("time") {
166
 			message.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
168
 			message.SetTag("time", time.Now().UTC().Format(IRCv3TimestampFormat))
167
 		}
169
 		}
168
 
170
 
172
 		}
174
 		}
173
 
175
 
174
 		// send message out
176
 		// send message out
175
-		rb.target.SendRawMessage(message, blocking)
177
+		rb.session.SendRawMessage(message, blocking)
176
 	}
178
 	}
177
 
179
 
178
 	// end batch if required
180
 	// end batch if required

+ 9
- 8
irc/roleplay.go View File

46
 		}
46
 		}
47
 
47
 
48
 		for _, member := range channel.Members() {
48
 		for _, member := range channel.Members() {
49
-			if member == client && !client.capabilities.Has(caps.EchoMessage) {
50
-				continue
51
-			}
52
-			if member == client {
53
-				rb.Add(nil, source, "PRIVMSG", channel.name, message)
54
-			} else {
55
-				member.Send(nil, source, "PRIVMSG", channel.name, message)
49
+			for _, session := range member.Sessions() {
50
+				if member == client && !session.capabilities.Has(caps.EchoMessage) {
51
+					continue
52
+				} else if rb.session == session {
53
+					rb.Add(nil, source, "PRIVMSG", channel.name, message)
54
+				} else if member == client || session.capabilities.Has(caps.EchoMessage) {
55
+					session.Send(nil, source, "PRIVMSG", channel.name, message)
56
+				}
56
 			}
57
 			}
57
 		}
58
 		}
58
 	} else {
59
 	} else {
71
 		cnick := client.Nick()
72
 		cnick := client.Nick()
72
 		tnick := user.Nick()
73
 		tnick := user.Nick()
73
 		user.Send(nil, source, "PRIVMSG", tnick, message)
74
 		user.Send(nil, source, "PRIVMSG", tnick, message)
74
-		if client.capabilities.Has(caps.EchoMessage) {
75
+		if rb.session.capabilities.Has(caps.EchoMessage) {
75
 			rb.Add(nil, source, "PRIVMSG", tnick, message)
76
 			rb.Add(nil, source, "PRIVMSG", tnick, message)
76
 		}
77
 		}
77
 		if user.HasMode(modes.Away) {
78
 		if user.HasMode(modes.Away) {

+ 49
- 46
irc/server.go View File

41
 	supportedChannelModesString = modes.SupportedChannelModes.String()
41
 	supportedChannelModesString = modes.SupportedChannelModes.String()
42
 
42
 
43
 	// SupportedCapabilities are the caps we advertise.
43
 	// SupportedCapabilities are the caps we advertise.
44
-	// MaxLine, SASL and STS are set during server startup.
45
-	SupportedCapabilities = caps.NewSet(caps.Acc, caps.AccountTag, caps.AccountNotify, caps.AwayNotify, caps.Batch, caps.CapNotify, caps.ChgHost, caps.EchoMessage, caps.ExtendedJoin, caps.InviteNotify, caps.LabeledResponse, caps.Languages, caps.MessageTags, caps.MultiPrefix, caps.Rename, caps.Resume, caps.ServerTime, caps.SetName, caps.UserhostInNames)
44
+	// MaxLine, SASL and STS may be unset during server startup / rehash.
45
+	SupportedCapabilities = caps.NewCompleteSet()
46
 
46
 
47
 	// CapValues are the actual values we advertise to v3.2 clients.
47
 	// CapValues are the actual values we advertise to v3.2 clients.
48
 	// actual values are set during server startup.
48
 	// actual values are set during server startup.
374
 // server functionality
374
 // server functionality
375
 //
375
 //
376
 
376
 
377
-func (server *Server) tryRegister(c *Client) {
377
+func (server *Server) tryRegister(c *Client, session *Session) {
378
 	resumed := false
378
 	resumed := false
379
 	// try to complete registration, either via RESUME token or normally
379
 	// try to complete registration, either via RESUME token or normally
380
 	if c.resumeDetails != nil {
380
 	if c.resumeDetails != nil {
383
 		}
383
 		}
384
 		resumed = true
384
 		resumed = true
385
 	} else {
385
 	} else {
386
-		if c.preregNick == "" || !c.HasUsername() || c.capState == caps.NegotiatingState {
386
+		if c.preregNick == "" || !c.HasUsername() || session.capState == caps.NegotiatingState {
387
 			return
387
 			return
388
 		}
388
 		}
389
 
389
 
391
 		// before completing the other registration commands
391
 		// before completing the other registration commands
392
 		config := server.Config()
392
 		config := server.Config()
393
 		if !c.isAuthorized(config) {
393
 		if !c.isAuthorized(config) {
394
-			c.Quit(c.t("Bad password"))
395
-			c.destroy(false)
394
+			c.Quit(c.t("Bad password"), nil)
395
+			c.destroy(false, nil)
396
 			return
396
 			return
397
 		}
397
 		}
398
 
398
 
399
-		rb := NewResponseBuffer(c)
400
-		nickAssigned := performNickChange(server, c, c, c.preregNick, rb)
399
+		rb := NewResponseBuffer(session)
400
+		nickAssigned := performNickChange(server, c, c, session, c.preregNick, rb)
401
 		rb.Send(true)
401
 		rb.Send(true)
402
 		if !nickAssigned {
402
 		if !nickAssigned {
403
 			c.preregNick = ""
403
 			c.preregNick = ""
407
 		// check KLINEs
407
 		// check KLINEs
408
 		isBanned, info := server.klines.CheckMasks(c.AllNickmasks()...)
408
 		isBanned, info := server.klines.CheckMasks(c.AllNickmasks()...)
409
 		if isBanned {
409
 		if isBanned {
410
-			c.Quit(info.BanMessage(c.t("You are banned from this server (%s)")))
411
-			c.destroy(false)
410
+			c.Quit(info.BanMessage(c.t("You are banned from this server (%s)")), nil)
411
+			c.destroy(false, nil)
412
 			return
412
 			return
413
 		}
413
 		}
414
 	}
414
 	}
415
 
415
 
416
-	// registration has succeeded:
417
-	c.SetRegistered()
416
+	reattached := session.client != c
418
 
417
 
419
-	// count new user in statistics
420
-	server.stats.ChangeTotal(1)
418
+	if !reattached {
419
+		// registration has succeeded:
420
+		c.SetRegistered()
421
 
421
 
422
-	if !resumed {
423
-		server.monitorManager.AlertAbout(c, true)
422
+		// count new user in statistics
423
+		server.stats.ChangeTotal(1)
424
+
425
+		if !resumed {
426
+			server.monitorManager.AlertAbout(c, true)
427
+		}
424
 	}
428
 	}
425
 
429
 
426
 	// continue registration
430
 	// continue registration
436
 	//TODO(dan): Look at adding last optional [<channel modes with a parameter>] parameter
440
 	//TODO(dan): Look at adding last optional [<channel modes with a parameter>] parameter
437
 	c.Send(nil, server.name, RPL_MYINFO, c.nick, server.name, Ver, supportedUserModesString, supportedChannelModesString)
441
 	c.Send(nil, server.name, RPL_MYINFO, c.nick, server.name, Ver, supportedUserModesString, supportedChannelModesString)
438
 
442
 
439
-	rb := NewResponseBuffer(c)
443
+	rb := NewResponseBuffer(session)
440
 	c.RplISupport(rb)
444
 	c.RplISupport(rb)
441
 	server.MOTD(c, rb)
445
 	server.MOTD(c, rb)
442
 	rb.Send(true)
446
 	rb.Send(true)
480
 }
484
 }
481
 
485
 
482
 // WhoisChannelsNames returns the common channel names between two users.
486
 // WhoisChannelsNames returns the common channel names between two users.
483
-func (client *Client) WhoisChannelsNames(target *Client) []string {
484
-	isMultiPrefix := client.capabilities.Has(caps.MultiPrefix)
487
+func (client *Client) WhoisChannelsNames(target *Client, multiPrefix bool) []string {
485
 	var chstrs []string
488
 	var chstrs []string
486
 	for _, channel := range target.Channels() {
489
 	for _, channel := range target.Channels() {
487
 		// channel is secret and the target can't see it
490
 		// channel is secret and the target can't see it
490
 				continue
493
 				continue
491
 			}
494
 			}
492
 		}
495
 		}
493
-		chstrs = append(chstrs, channel.ClientPrefixes(target, isMultiPrefix)+channel.name)
496
+		chstrs = append(chstrs, channel.ClientPrefixes(target, multiPrefix)+channel.name)
494
 	}
497
 	}
495
 	return chstrs
498
 	return chstrs
496
 }
499
 }
501
 	rb.Add(nil, client.server.name, RPL_WHOISUSER, cnick, targetInfo.nick, targetInfo.username, targetInfo.hostname, "*", targetInfo.realname)
504
 	rb.Add(nil, client.server.name, RPL_WHOISUSER, cnick, targetInfo.nick, targetInfo.username, targetInfo.hostname, "*", targetInfo.realname)
502
 	tnick := targetInfo.nick
505
 	tnick := targetInfo.nick
503
 
506
 
504
-	whoischannels := client.WhoisChannelsNames(target)
507
+	whoischannels := client.WhoisChannelsNames(target, rb.session.capabilities.Has(caps.MultiPrefix))
505
 	if whoischannels != nil {
508
 	if whoischannels != nil {
506
 		rb.Add(nil, client.server.name, RPL_WHOISCHANNELS, cnick, tnick, strings.Join(whoischannels, " "))
509
 		rb.Add(nil, client.server.name, RPL_WHOISCHANNELS, cnick, tnick, strings.Join(whoischannels, " "))
507
 	}
510
 	}
555
 	}
558
 	}
556
 
559
 
557
 	if channel != nil {
560
 	if channel != nil {
558
-		flags += channel.ClientPrefixes(client, target.capabilities.Has(caps.MultiPrefix))
561
+		// TODO is this right?
562
+		flags += channel.ClientPrefixes(client, rb.session.capabilities.Has(caps.MultiPrefix))
559
 		channelName = channel.name
563
 		channelName = channel.name
560
 	}
564
 	}
561
-	rb.Add(nil, target.server.name, RPL_WHOREPLY, target.nick, channelName, client.Username(), client.Hostname(), client.server.name, client.Nick(), flags, strconv.Itoa(client.hops)+" "+client.Realname())
562
-}
563
-
564
-func whoChannel(client *Client, channel *Channel, friends ClientSet, rb *ResponseBuffer) {
565
-	for _, member := range channel.Members() {
566
-		if !client.HasMode(modes.Invisible) || friends[client] {
567
-			client.rplWhoReply(channel, member, rb)
568
-		}
569
-	}
565
+	// hardcode a hopcount of 0 for now
566
+	rb.Add(nil, target.server.name, RPL_WHOREPLY, target.nick, channelName, client.Username(), client.Hostname(), client.server.name, client.Nick(), flags, "0 "+client.Realname())
570
 }
567
 }
571
 
568
 
572
 // rehash reloads the config and applies the changes from the config file.
569
 // rehash reloads the config and applies the changes from the config file.
691
 		SupportedCapabilities.Enable(caps.MaxLine)
688
 		SupportedCapabilities.Enable(caps.MaxLine)
692
 		value := fmt.Sprintf("%d", config.Limits.LineLen.Rest)
689
 		value := fmt.Sprintf("%d", config.Limits.LineLen.Rest)
693
 		CapValues.Set(caps.MaxLine, value)
690
 		CapValues.Set(caps.MaxLine, value)
691
+	} else {
692
+		SupportedCapabilities.Disable(caps.MaxLine)
694
 	}
693
 	}
695
 
694
 
696
 	// STS
695
 	// STS
699
 	stsDisabledByRehash := false
698
 	stsDisabledByRehash := false
700
 	stsCurrentCapValue, _ := CapValues.Get(caps.STS)
699
 	stsCurrentCapValue, _ := CapValues.Get(caps.STS)
701
 	server.logger.Debug("server", "STS Vals", stsCurrentCapValue, stsValue, fmt.Sprintf("server[%v] config[%v]", stsPreviouslyEnabled, config.Server.STS.Enabled))
700
 	server.logger.Debug("server", "STS Vals", stsCurrentCapValue, stsValue, fmt.Sprintf("server[%v] config[%v]", stsPreviouslyEnabled, config.Server.STS.Enabled))
702
-	if config.Server.STS.Enabled && !stsPreviouslyEnabled {
701
+	if config.Server.STS.Enabled {
703
 		// enabling STS
702
 		// enabling STS
704
 		SupportedCapabilities.Enable(caps.STS)
703
 		SupportedCapabilities.Enable(caps.STS)
705
-		addedCaps.Add(caps.STS)
706
-		CapValues.Set(caps.STS, stsValue)
707
-	} else if !config.Server.STS.Enabled && stsPreviouslyEnabled {
704
+		if !stsPreviouslyEnabled {
705
+			addedCaps.Add(caps.STS)
706
+			CapValues.Set(caps.STS, stsValue)
707
+		} else if stsValue != stsCurrentCapValue {
708
+			// STS policy updated
709
+			CapValues.Set(caps.STS, stsValue)
710
+			updatedCaps.Add(caps.STS)
711
+		}
712
+	} else {
708
 		// disabling STS
713
 		// disabling STS
709
 		SupportedCapabilities.Disable(caps.STS)
714
 		SupportedCapabilities.Disable(caps.STS)
710
-		removedCaps.Add(caps.STS)
711
-		stsDisabledByRehash = true
712
-	} else if config.Server.STS.Enabled && stsPreviouslyEnabled && stsValue != stsCurrentCapValue {
713
-		// STS policy updated
714
-		CapValues.Set(caps.STS, stsValue)
715
-		updatedCaps.Add(caps.STS)
715
+		if stsPreviouslyEnabled {
716
+			removedCaps.Add(caps.STS)
717
+			stsDisabledByRehash = true
718
+		}
716
 	}
719
 	}
717
 
720
 
718
 	// resize history buffers as needed
721
 	// resize history buffers as needed
730
 	}
733
 	}
731
 
734
 
732
 	// burst new and removed caps
735
 	// burst new and removed caps
733
-	var capBurstClients ClientSet
736
+	var capBurstSessions []*Session
734
 	added := make(map[caps.Version]string)
737
 	added := make(map[caps.Version]string)
735
 	var removed string
738
 	var removed string
736
 
739
 
741
 	removedCaps.Union(updatedCaps)
744
 	removedCaps.Union(updatedCaps)
742
 
745
 
743
 	if !addedCaps.Empty() || !removedCaps.Empty() {
746
 	if !addedCaps.Empty() || !removedCaps.Empty() {
744
-		capBurstClients = server.clients.AllWithCaps(caps.CapNotify)
747
+		capBurstSessions = server.clients.AllWithCaps(caps.CapNotify)
745
 
748
 
746
 		added[caps.Cap301] = addedCaps.String(caps.Cap301, CapValues)
749
 		added[caps.Cap301] = addedCaps.String(caps.Cap301, CapValues)
747
 		added[caps.Cap302] = addedCaps.String(caps.Cap302, CapValues)
750
 		added[caps.Cap302] = addedCaps.String(caps.Cap302, CapValues)
749
 		removed = removedCaps.String(caps.Cap301, CapValues)
752
 		removed = removedCaps.String(caps.Cap301, CapValues)
750
 	}
753
 	}
751
 
754
 
752
-	for sClient := range capBurstClients {
755
+	for _, sSession := range capBurstSessions {
753
 		if stsDisabledByRehash {
756
 		if stsDisabledByRehash {
754
 			// remove STS policy
757
 			// remove STS policy
755
 			//TODO(dan): this is an ugly hack. we can write this better.
758
 			//TODO(dan): this is an ugly hack. we can write this better.
763
 		}
766
 		}
764
 		// DEL caps and then send NEW ones so that updated caps get removed/added correctly
767
 		// DEL caps and then send NEW ones so that updated caps get removed/added correctly
765
 		if !removedCaps.Empty() {
768
 		if !removedCaps.Empty() {
766
-			sClient.Send(nil, server.name, "CAP", sClient.nick, "DEL", removed)
769
+			sSession.Send(nil, server.name, "CAP", sSession.client.Nick(), "DEL", removed)
767
 		}
770
 		}
768
 		if !addedCaps.Empty() {
771
 		if !addedCaps.Empty() {
769
-			sClient.Send(nil, server.name, "CAP", sClient.nick, "NEW", added[sClient.capVersion])
772
+			sSession.Send(nil, server.name, "CAP", sSession.client.Nick(), "NEW", added[sSession.capVersion])
770
 		}
773
 		}
771
 	}
774
 	}
772
 
775
 

+ 13
- 0
oragono.yaml View File

262
         # rename-prefix - this is the prefix to use when renaming clients (e.g. Guest-AB54U31)
262
         # rename-prefix - this is the prefix to use when renaming clients (e.g. Guest-AB54U31)
263
         rename-prefix: Guest-
263
         rename-prefix: Guest-
264
 
264
 
265
+    # bouncer controls whether oragono can act as a bouncer, i.e., allowing
266
+    # multiple connections to attach to the same client/nickname identity
267
+    bouncer:
268
+        # when disabled, each connection must use a separate nickname (as is the
269
+        # typical behavior of IRC servers). when enabled, a new connection that
270
+        # has authenticated with SASL can associate itself with an existing
271
+        # client
272
+        enabled: true
273
+
274
+        # clients can opt in to bouncer functionality using the cap system, or
275
+        # via nickserv. if this is enabled, then they have to opt out instead
276
+        allowed-by-default: false
277
+
265
     # vhosts controls the assignment of vhosts (strings displayed in place of the user's
278
     # vhosts controls the assignment of vhosts (strings displayed in place of the user's
266
     # hostname/IP) by the HostServ service
279
     # hostname/IP) by the HostServ service
267
     vhosts:
280
     vhosts:

Loading…
Cancel
Save