source: code/trunk/upstream.go@ 806

Last change on this file since 806 was 804, checked in by koizumi.aoi, 2 years ago

Drunk as I like

Signed-off-by: Aoi K <koizumi.aoi@…>

File size: 55.6 KB
Line 
1package suika
2
3import (
4 "context"
5 "crypto"
6 "crypto/sha256"
7 "crypto/tls"
8 "crypto/x509"
9 "encoding/base64"
10 "errors"
11 "fmt"
12 "io"
13 "net"
14 "strconv"
15 "strings"
16 "time"
17
18 "github.com/emersion/go-sasl"
19 "gopkg.in/irc.v3"
20)
21
22// permanentUpstreamCaps is the static list of upstream capabilities always
23// requested when supported.
24var permanentUpstreamCaps = map[string]bool{
25 "account-notify": true,
26 "account-tag": true,
27 "away-notify": true,
28 "batch": true,
29 "extended-join": true,
30 "invite-notify": true,
31 "labeled-response": true,
32 "message-tags": true,
33 "multi-prefix": true,
34 "sasl": true,
35 "server-time": true,
36 "setname": true,
37
38 "draft/account-registration": true,
39 "draft/extended-monitor": true,
40}
41
42type registrationError struct {
43 *irc.Message
44}
45
46func (err registrationError) Error() string {
47 return fmt.Sprintf("registration error (%v): %v", err.Command, err.Reason())
48}
49
50func (err registrationError) Reason() string {
51 if len(err.Params) > 0 {
52 return err.Params[len(err.Params)-1]
53 }
54 return err.Command
55}
56
57func (err registrationError) Temporary() bool {
58 // Only return false if we're 100% sure that fixing the error requires a
59 // network configuration change
60 switch err.Command {
61 case irc.ERR_PASSWDMISMATCH, irc.ERR_ERRONEUSNICKNAME:
62 return false
63 case "FAIL":
64 return err.Params[1] != "ACCOUNT_REQUIRED"
65 default:
66 return true
67 }
68}
69
70type upstreamChannel struct {
71 Name string
72 conn *upstreamConn
73 Topic string
74 TopicWho *irc.Prefix
75 TopicTime time.Time
76 Status channelStatus
77 modes channelModes
78 creationTime string
79 Members membershipsCasemapMap
80 complete bool
81 detachTimer *time.Timer
82}
83
84func (uc *upstreamChannel) updateAutoDetach(dur time.Duration) {
85 if uc.detachTimer != nil {
86 uc.detachTimer.Stop()
87 uc.detachTimer = nil
88 }
89
90 if dur == 0 {
91 return
92 }
93
94 uc.detachTimer = time.AfterFunc(dur, func() {
95 uc.conn.network.user.events <- eventChannelDetach{
96 uc: uc.conn,
97 name: uc.Name,
98 }
99 })
100}
101
102type pendingUpstreamCommand struct {
103 downstreamID uint64
104 msg *irc.Message
105}
106
107type upstreamConn struct {
108 conn
109
110 network *network
111 user *user
112
113 serverName string
114 availableUserModes string
115 availableChannelModes map[byte]channelModeType
116 availableChannelTypes string
117 availableMemberships []membership
118 isupport map[string]*string
119
120 registered bool
121 nick string
122 nickCM string
123 username string
124 realname string
125 modes userModes
126 channels upstreamChannelCasemapMap
127 supportedCaps map[string]string
128 caps map[string]bool
129 batches map[string]batch
130 away bool
131 account string
132 nextLabelID uint64
133 monitored monitorCasemapMap
134
135 saslClient sasl.Client
136 saslStarted bool
137
138 casemapIsSet bool
139
140 // Queue of commands in progress, indexed by type. The first entry has been
141 // sent to the server and is awaiting reply. The following entries have not
142 // been sent yet.
143 pendingCmds map[string][]pendingUpstreamCommand
144
145 gotMotd bool
146}
147
148func connectToUpstream(ctx context.Context, network *network) (*upstreamConn, error) {
149 logger := &prefixLogger{network.user.logger, fmt.Sprintf("upstream %q: ", network.GetName())}
150
151 dialer := net.Dialer{Timeout: connectTimeout}
152
153 u, err := network.URL()
154 if err != nil {
155 return nil, err
156 }
157
158 var netConn net.Conn
159 switch u.Scheme {
160 case "ircs":
161 addr := u.Host
162 host, _, err := net.SplitHostPort(u.Host)
163 if err != nil {
164 host = u.Host
165 addr = u.Host + ":6697"
166 }
167
168 dialer.LocalAddr, err = network.user.localTCPAddrForHost(ctx, host)
169 if err != nil {
170 return nil, fmt.Errorf("failed to pick local IP for remote host %q: %v", host, err)
171 }
172
173 logger.Printf("connecting to TLS server at address %q", addr)
174
175 tlsConfig := &tls.Config{ServerName: host, NextProtos: []string{"irc"}}
176 if network.SASL.Mechanism == "EXTERNAL" {
177 if network.SASL.External.CertBlob == nil {
178 return nil, fmt.Errorf("missing certificate for authentication")
179 }
180 if network.SASL.External.PrivKeyBlob == nil {
181 return nil, fmt.Errorf("missing private key for authentication")
182 }
183 key, err := x509.ParsePKCS8PrivateKey(network.SASL.External.PrivKeyBlob)
184 if err != nil {
185 return nil, fmt.Errorf("failed to parse private key: %v", err)
186 }
187 tlsConfig.Certificates = []tls.Certificate{
188 {
189 Certificate: [][]byte{network.SASL.External.CertBlob},
190 PrivateKey: key.(crypto.PrivateKey),
191 },
192 }
193 logger.Printf("using TLS client certificate %x", sha256.Sum256(network.SASL.External.CertBlob))
194 }
195
196 netConn, err = dialer.DialContext(ctx, "tcp", addr)
197 if err != nil {
198 return nil, fmt.Errorf("failed to dial %q: %v", addr, err)
199 }
200
201 // Don't do the TLS handshake immediately, because we need to register
202 // the new connection with identd ASAP.
203 netConn = tls.Client(netConn, tlsConfig)
204 case "irc+insecure":
205 addr := u.Host
206 host, _, err := net.SplitHostPort(addr)
207 if err != nil {
208 host = u.Host
209 addr = u.Host + ":6667"
210 }
211
212 dialer.LocalAddr, err = network.user.localTCPAddrForHost(ctx, host)
213 if err != nil {
214 return nil, fmt.Errorf("failed to pick local IP for remote host %q: %v", host, err)
215 }
216
217 logger.Printf("connecting to plain-text server at address %q", addr)
218 netConn, err = dialer.DialContext(ctx, "tcp", addr)
219 if err != nil {
220 return nil, fmt.Errorf("failed to dial %q: %v", addr, err)
221 }
222 case "irc+unix", "unix":
223 logger.Printf("connecting to Unix socket at path %q", u.Path)
224 netConn, err = dialer.DialContext(ctx, "unix", u.Path)
225 if err != nil {
226 return nil, fmt.Errorf("failed to connect to Unix socket %q: %v", u.Path, err)
227 }
228 default:
229 return nil, fmt.Errorf("failed to dial %q: unknown scheme: %v", network.Addr, u.Scheme)
230 }
231
232 options := connOptions{
233 Logger: logger,
234 RateLimitDelay: upstreamMessageDelay,
235 RateLimitBurst: upstreamMessageBurst,
236 }
237
238 uc := &upstreamConn{
239 conn: *newConn(network.user.srv, newNetIRCConn(netConn), &options),
240 network: network,
241 user: network.user,
242 channels: upstreamChannelCasemapMap{newCasemapMap(0)},
243 supportedCaps: make(map[string]string),
244 caps: make(map[string]bool),
245 batches: make(map[string]batch),
246 availableChannelTypes: stdChannelTypes,
247 availableChannelModes: stdChannelModes,
248 availableMemberships: stdMemberships,
249 isupport: make(map[string]*string),
250 pendingCmds: make(map[string][]pendingUpstreamCommand),
251 monitored: monitorCasemapMap{newCasemapMap(0)},
252 }
253 return uc, nil
254}
255
256func (uc *upstreamConn) forEachDownstream(f func(*downstreamConn)) {
257 uc.network.forEachDownstream(f)
258}
259
260func (uc *upstreamConn) forEachDownstreamByID(id uint64, f func(*downstreamConn)) {
261 uc.forEachDownstream(func(dc *downstreamConn) {
262 if id != 0 && id != dc.id {
263 return
264 }
265 f(dc)
266 })
267}
268
269func (uc *upstreamConn) downstreamByID(id uint64) *downstreamConn {
270 for _, dc := range uc.user.downstreamConns {
271 if dc.id == id {
272 return dc
273 }
274 }
275 return nil
276}
277
278func (uc *upstreamConn) getChannel(name string) (*upstreamChannel, error) {
279 ch := uc.channels.Value(name)
280 if ch == nil {
281 return nil, fmt.Errorf("unknown channel %q", name)
282 }
283 return ch, nil
284}
285
286func (uc *upstreamConn) isChannel(entity string) bool {
287 return strings.ContainsRune(uc.availableChannelTypes, rune(entity[0]))
288}
289
290func (uc *upstreamConn) isOurNick(nick string) bool {
291 return uc.nickCM == uc.network.casemap(nick)
292}
293
294func (uc *upstreamConn) abortPendingCommands() {
295 for _, l := range uc.pendingCmds {
296 for _, pendingCmd := range l {
297 dc := uc.downstreamByID(pendingCmd.downstreamID)
298 if dc == nil {
299 continue
300 }
301
302 switch pendingCmd.msg.Command {
303 case "LIST":
304 dc.SendMessage(&irc.Message{
305 Prefix: dc.srv.prefix(),
306 Command: irc.RPL_LISTEND,
307 Params: []string{dc.nick, "Command aborted"},
308 })
309 case "WHO":
310 mask := "*"
311 if len(pendingCmd.msg.Params) > 0 {
312 mask = pendingCmd.msg.Params[0]
313 }
314 dc.SendMessage(&irc.Message{
315 Prefix: dc.srv.prefix(),
316 Command: irc.RPL_ENDOFWHO,
317 Params: []string{dc.nick, mask, "Command aborted"},
318 })
319 case "AUTHENTICATE":
320 dc.endSASL(&irc.Message{
321 Prefix: dc.srv.prefix(),
322 Command: irc.ERR_SASLABORTED,
323 Params: []string{dc.nick, "SASL authentication aborted"},
324 })
325 case "REGISTER", "VERIFY":
326 dc.SendMessage(&irc.Message{
327 Prefix: dc.srv.prefix(),
328 Command: "FAIL",
329 Params: []string{pendingCmd.msg.Command, "TEMPORARILY_UNAVAILABLE", pendingCmd.msg.Params[0], "Command aborted"},
330 })
331 default:
332 panic(fmt.Errorf("Unsupported pending command %q", pendingCmd.msg.Command))
333 }
334 }
335 }
336
337 uc.pendingCmds = make(map[string][]pendingUpstreamCommand)
338}
339
340func (uc *upstreamConn) sendNextPendingCommand(cmd string) {
341 if len(uc.pendingCmds[cmd]) == 0 {
342 return
343 }
344 uc.SendMessage(context.TODO(), uc.pendingCmds[cmd][0].msg)
345}
346
347func (uc *upstreamConn) enqueueCommand(dc *downstreamConn, msg *irc.Message) {
348 switch msg.Command {
349 case "LIST", "WHO", "AUTHENTICATE", "REGISTER", "VERIFY":
350 // Supported
351 default:
352 panic(fmt.Errorf("Unsupported pending command %q", msg.Command))
353 }
354
355 uc.pendingCmds[msg.Command] = append(uc.pendingCmds[msg.Command], pendingUpstreamCommand{
356 downstreamID: dc.id,
357 msg: msg,
358 })
359
360 if len(uc.pendingCmds[msg.Command]) == 1 {
361 uc.sendNextPendingCommand(msg.Command)
362 }
363}
364
365func (uc *upstreamConn) currentPendingCommand(cmd string) (*downstreamConn, *irc.Message) {
366 if len(uc.pendingCmds[cmd]) == 0 {
367 return nil, nil
368 }
369
370 pendingCmd := uc.pendingCmds[cmd][0]
371 return uc.downstreamByID(pendingCmd.downstreamID), pendingCmd.msg
372}
373
374func (uc *upstreamConn) dequeueCommand(cmd string) (*downstreamConn, *irc.Message) {
375 dc, msg := uc.currentPendingCommand(cmd)
376
377 if len(uc.pendingCmds[cmd]) > 0 {
378 copy(uc.pendingCmds[cmd], uc.pendingCmds[cmd][1:])
379 uc.pendingCmds[cmd] = uc.pendingCmds[cmd][:len(uc.pendingCmds[cmd])-1]
380 }
381
382 uc.sendNextPendingCommand(cmd)
383
384 return dc, msg
385}
386
387func (uc *upstreamConn) cancelPendingCommandsByDownstreamID(downstreamID uint64) {
388 for cmd := range uc.pendingCmds {
389 // We can't cancel the currently running command stored in
390 // uc.pendingCmds[cmd][0]
391 for i := len(uc.pendingCmds[cmd]) - 1; i >= 1; i-- {
392 if uc.pendingCmds[cmd][i].downstreamID == downstreamID {
393 uc.pendingCmds[cmd] = append(uc.pendingCmds[cmd][:i], uc.pendingCmds[cmd][i+1:]...)
394 }
395 }
396 }
397}
398
399func (uc *upstreamConn) parseMembershipPrefix(s string) (ms *memberships, nick string) {
400 memberships := make(memberships, 0, 4)
401 i := 0
402 for _, m := range uc.availableMemberships {
403 if i >= len(s) {
404 break
405 }
406 if s[i] == m.Prefix {
407 memberships = append(memberships, m)
408 i++
409 }
410 }
411 return &memberships, s[i:]
412}
413
414func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) error {
415 var label string
416 if l, ok := msg.GetTag("label"); ok {
417 label = l
418 delete(msg.Tags, "label")
419 }
420
421 var msgBatch *batch
422 if batchName, ok := msg.GetTag("batch"); ok {
423 b, ok := uc.batches[batchName]
424 if !ok {
425 return fmt.Errorf("unexpected batch reference: batch was not defined: %q", batchName)
426 }
427 msgBatch = &b
428 if label == "" {
429 label = msgBatch.Label
430 }
431 delete(msg.Tags, "batch")
432 }
433
434 var downstreamID uint64 = 0
435 if label != "" {
436 var labelOffset uint64
437 n, err := fmt.Sscanf(label, "sd-%d-%d", &downstreamID, &labelOffset)
438 if err == nil && n < 2 {
439 err = errors.New("not enough arguments")
440 }
441 if err != nil {
442 return fmt.Errorf("unexpected message label: invalid downstream reference for label %q: %v", label, err)
443 }
444 }
445
446 if _, ok := msg.Tags["time"]; !ok {
447 msg.Tags["time"] = irc.TagValue(formatServerTime(time.Now()))
448 }
449
450 switch msg.Command {
451 case "PING":
452 uc.SendMessage(ctx, &irc.Message{
453 Command: "PONG",
454 Params: msg.Params,
455 })
456 return nil
457 case "NOTICE", "PRIVMSG", "TAGMSG":
458 if msg.Prefix == nil {
459 return fmt.Errorf("expected a prefix")
460 }
461
462 var entity, text string
463 if msg.Command != "TAGMSG" {
464 if err := parseMessageParams(msg, &entity, &text); err != nil {
465 return err
466 }
467 } else {
468 if err := parseMessageParams(msg, &entity); err != nil {
469 return err
470 }
471 }
472
473 if msg.Prefix.Name == serviceNick {
474 uc.logger.Printf("skipping %v from suika's service: %v", msg.Command, msg)
475 break
476 }
477 if entity == serviceNick {
478 uc.logger.Printf("skipping %v to suika's service: %v", msg.Command, msg)
479 break
480 }
481
482 if msg.Prefix.User == "" && msg.Prefix.Host == "" { // server message
483 uc.produce("", msg, nil)
484 } else { // regular user message
485 target := entity
486 if uc.isOurNick(target) {
487 target = msg.Prefix.Name
488 }
489
490 ch := uc.network.channels.Value(target)
491 if ch != nil && msg.Command != "TAGMSG" {
492 if ch.Detached {
493 uc.handleDetachedMessage(ctx, ch, msg)
494 }
495
496 highlight := uc.network.isHighlight(msg)
497 if ch.DetachOn == FilterMessage || ch.DetachOn == FilterDefault || (ch.DetachOn == FilterHighlight && highlight) {
498 uc.updateChannelAutoDetach(target)
499 }
500 }
501
502 uc.produce(target, msg, nil)
503 }
504 case "CAP":
505 var subCmd string
506 if err := parseMessageParams(msg, nil, &subCmd); err != nil {
507 return err
508 }
509 subCmd = strings.ToUpper(subCmd)
510 subParams := msg.Params[2:]
511 switch subCmd {
512 case "LS":
513 if len(subParams) < 1 {
514 return newNeedMoreParamsError(msg.Command)
515 }
516 caps := subParams[len(subParams)-1]
517 more := len(subParams) >= 2 && msg.Params[len(subParams)-2] == "*"
518
519 uc.handleSupportedCaps(caps)
520
521 if more {
522 break // wait to receive all capabilities
523 }
524
525 uc.requestCaps()
526
527 if uc.requestSASL() {
528 break // we'll send CAP END after authentication is completed
529 }
530
531 uc.SendMessage(ctx, &irc.Message{
532 Command: "CAP",
533 Params: []string{"END"},
534 })
535 case "ACK", "NAK":
536 if len(subParams) < 1 {
537 return newNeedMoreParamsError(msg.Command)
538 }
539 caps := strings.Fields(subParams[0])
540
541 for _, name := range caps {
542 if err := uc.handleCapAck(ctx, strings.ToLower(name), subCmd == "ACK"); err != nil {
543 return err
544 }
545 }
546
547 if uc.registered {
548 uc.forEachDownstream(func(dc *downstreamConn) {
549 dc.updateSupportedCaps()
550 })
551 }
552 case "NEW":
553 if len(subParams) < 1 {
554 return newNeedMoreParamsError(msg.Command)
555 }
556 uc.handleSupportedCaps(subParams[0])
557 uc.requestCaps()
558 case "DEL":
559 if len(subParams) < 1 {
560 return newNeedMoreParamsError(msg.Command)
561 }
562 caps := strings.Fields(subParams[0])
563
564 for _, c := range caps {
565 delete(uc.supportedCaps, c)
566 delete(uc.caps, c)
567 }
568
569 if uc.registered {
570 uc.forEachDownstream(func(dc *downstreamConn) {
571 dc.updateSupportedCaps()
572 })
573 }
574 default:
575 uc.logger.Printf("unhandled message: %v", msg)
576 }
577 case "AUTHENTICATE":
578 if uc.saslClient == nil {
579 return fmt.Errorf("received unexpected AUTHENTICATE message")
580 }
581
582 // TODO: if a challenge is 400 bytes long, buffer it
583 var challengeStr string
584 if err := parseMessageParams(msg, &challengeStr); err != nil {
585 uc.SendMessage(ctx, &irc.Message{
586 Command: "AUTHENTICATE",
587 Params: []string{"*"},
588 })
589 return err
590 }
591
592 var challenge []byte
593 if challengeStr != "+" {
594 var err error
595 challenge, err = base64.StdEncoding.DecodeString(challengeStr)
596 if err != nil {
597 uc.SendMessage(ctx, &irc.Message{
598 Command: "AUTHENTICATE",
599 Params: []string{"*"},
600 })
601 return err
602 }
603 }
604
605 var resp []byte
606 var err error
607 if !uc.saslStarted {
608 _, resp, err = uc.saslClient.Start()
609 uc.saslStarted = true
610 } else {
611 resp, err = uc.saslClient.Next(challenge)
612 }
613 if err != nil {
614 uc.SendMessage(ctx, &irc.Message{
615 Command: "AUTHENTICATE",
616 Params: []string{"*"},
617 })
618 return err
619 }
620
621 // <= instead of < because we need to send a final empty response if
622 // the last chunk is exactly 400 bytes long
623 for i := 0; i <= len(resp); i += maxSASLLength {
624 j := i + maxSASLLength
625 if j > len(resp) {
626 j = len(resp)
627 }
628
629 chunk := resp[i:j]
630
631 var respStr = "+"
632 if len(chunk) != 0 {
633 respStr = base64.StdEncoding.EncodeToString(chunk)
634 }
635
636 uc.SendMessage(ctx, &irc.Message{
637 Command: "AUTHENTICATE",
638 Params: []string{respStr},
639 })
640 }
641 case irc.RPL_LOGGEDIN:
642 if err := parseMessageParams(msg, nil, nil, &uc.account); err != nil {
643 return err
644 }
645 uc.logger.Printf("logged in with account %q", uc.account)
646 uc.forEachDownstream(func(dc *downstreamConn) {
647 dc.updateAccount()
648 })
649 case irc.RPL_LOGGEDOUT:
650 uc.account = ""
651 uc.logger.Printf("logged out")
652 uc.forEachDownstream(func(dc *downstreamConn) {
653 dc.updateAccount()
654 })
655 case irc.ERR_NICKLOCKED, irc.RPL_SASLSUCCESS, irc.ERR_SASLFAIL, irc.ERR_SASLTOOLONG, irc.ERR_SASLABORTED:
656 var info string
657 if err := parseMessageParams(msg, nil, &info); err != nil {
658 return err
659 }
660 switch msg.Command {
661 case irc.ERR_NICKLOCKED:
662 uc.logger.Printf("invalid nick used with SASL authentication: %v", info)
663 case irc.ERR_SASLFAIL:
664 uc.logger.Printf("SASL authentication failed: %v", info)
665 case irc.ERR_SASLTOOLONG:
666 uc.logger.Printf("SASL message too long: %v", info)
667 }
668
669 uc.saslClient = nil
670 uc.saslStarted = false
671
672 if dc, _ := uc.dequeueCommand("AUTHENTICATE"); dc != nil && dc.sasl != nil {
673 if msg.Command == irc.RPL_SASLSUCCESS {
674 uc.network.autoSaveSASLPlain(ctx, dc.sasl.plainUsername, dc.sasl.plainPassword)
675 }
676
677 dc.endSASL(msg)
678 }
679
680 if !uc.registered {
681 uc.SendMessage(ctx, &irc.Message{
682 Command: "CAP",
683 Params: []string{"END"},
684 })
685 }
686 case "REGISTER", "VERIFY":
687 if dc, cmd := uc.dequeueCommand(msg.Command); dc != nil {
688 if msg.Command == "REGISTER" {
689 var account, password string
690 if err := parseMessageParams(msg, nil, &account); err != nil {
691 return err
692 }
693 if err := parseMessageParams(cmd, nil, nil, &password); err != nil {
694 return err
695 }
696 uc.network.autoSaveSASLPlain(ctx, account, password)
697 }
698
699 dc.SendMessage(msg)
700 }
701 case irc.RPL_WELCOME:
702 if err := parseMessageParams(msg, &uc.nick); err != nil {
703 return err
704 }
705
706 uc.registered = true
707 uc.nickCM = uc.network.casemap(uc.nick)
708 uc.logger.Printf("connection registered with nick %q", uc.nick)
709
710 if uc.network.channels.Len() > 0 {
711 var channels, keys []string
712 for _, entry := range uc.network.channels.innerMap {
713 ch := entry.value.(*Channel)
714 channels = append(channels, ch.Name)
715 keys = append(keys, ch.Key)
716 }
717
718 for _, msg := range join(channels, keys) {
719 uc.SendMessage(ctx, msg)
720 }
721 }
722 case irc.RPL_MYINFO:
723 if err := parseMessageParams(msg, nil, &uc.serverName, nil, &uc.availableUserModes, nil); err != nil {
724 return err
725 }
726 case irc.RPL_ISUPPORT:
727 if err := parseMessageParams(msg, nil, nil); err != nil {
728 return err
729 }
730
731 var downstreamIsupport []string
732 for _, token := range msg.Params[1 : len(msg.Params)-1] {
733 parameter := token
734 var negate, hasValue bool
735 var value string
736 if strings.HasPrefix(token, "-") {
737 negate = true
738 token = token[1:]
739 } else if i := strings.IndexByte(token, '='); i >= 0 {
740 parameter = token[:i]
741 value = token[i+1:]
742 hasValue = true
743 }
744
745 if hasValue {
746 uc.isupport[parameter] = &value
747 } else if !negate {
748 uc.isupport[parameter] = nil
749 } else {
750 delete(uc.isupport, parameter)
751 }
752
753 var err error
754 switch parameter {
755 case "CASEMAPPING":
756 casemap, ok := parseCasemappingToken(value)
757 if !ok {
758 casemap = casemapRFC1459
759 }
760 uc.network.updateCasemapping(casemap)
761 uc.nickCM = uc.network.casemap(uc.nick)
762 uc.casemapIsSet = true
763 case "CHANMODES":
764 if !negate {
765 err = uc.handleChanModes(value)
766 } else {
767 uc.availableChannelModes = stdChannelModes
768 }
769 case "CHANTYPES":
770 if !negate {
771 uc.availableChannelTypes = value
772 } else {
773 uc.availableChannelTypes = stdChannelTypes
774 }
775 case "PREFIX":
776 if !negate {
777 err = uc.handleMemberships(value)
778 } else {
779 uc.availableMemberships = stdMemberships
780 }
781 }
782 if err != nil {
783 return err
784 }
785
786 if passthroughIsupport[parameter] {
787 downstreamIsupport = append(downstreamIsupport, token)
788 }
789 }
790
791 uc.updateMonitor()
792
793 uc.forEachDownstream(func(dc *downstreamConn) {
794 if dc.network == nil {
795 return
796 }
797 msgs := generateIsupport(dc.srv.prefix(), dc.nick, downstreamIsupport)
798 for _, msg := range msgs {
799 dc.SendMessage(msg)
800 }
801 })
802 case irc.ERR_NOMOTD, irc.RPL_ENDOFMOTD:
803 if !uc.casemapIsSet {
804 // upstream did not send any CASEMAPPING token, thus
805 // we assume it implements the old RFCs with rfc1459.
806 uc.casemapIsSet = true
807 uc.network.updateCasemapping(casemapRFC1459)
808 uc.nickCM = uc.network.casemap(uc.nick)
809 }
810
811 if !uc.gotMotd {
812 // Ignore the initial MOTD upon connection, but forward
813 // subsequent MOTD messages downstream
814 uc.gotMotd = true
815 return nil
816 }
817
818 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
819 dc.SendMessage(&irc.Message{
820 Prefix: uc.srv.prefix(),
821 Command: msg.Command,
822 Params: msg.Params,
823 })
824 })
825 case "BATCH":
826 var tag string
827 if err := parseMessageParams(msg, &tag); err != nil {
828 return err
829 }
830
831 if strings.HasPrefix(tag, "+") {
832 tag = tag[1:]
833 if _, ok := uc.batches[tag]; ok {
834 return fmt.Errorf("unexpected BATCH reference tag: batch was already defined: %q", tag)
835 }
836 var batchType string
837 if err := parseMessageParams(msg, nil, &batchType); err != nil {
838 return err
839 }
840 label := label
841 if label == "" && msgBatch != nil {
842 label = msgBatch.Label
843 }
844 uc.batches[tag] = batch{
845 Type: batchType,
846 Params: msg.Params[2:],
847 Outer: msgBatch,
848 Label: label,
849 }
850 } else if strings.HasPrefix(tag, "-") {
851 tag = tag[1:]
852 if _, ok := uc.batches[tag]; !ok {
853 return fmt.Errorf("unknown BATCH reference tag: %q", tag)
854 }
855 delete(uc.batches, tag)
856 } else {
857 return fmt.Errorf("unexpected BATCH reference tag: missing +/- prefix: %q", tag)
858 }
859 case "NICK":
860 if msg.Prefix == nil {
861 return fmt.Errorf("expected a prefix")
862 }
863
864 var newNick string
865 if err := parseMessageParams(msg, &newNick); err != nil {
866 return err
867 }
868
869 me := false
870 if uc.isOurNick(msg.Prefix.Name) {
871 uc.logger.Printf("changed nick from %q to %q", uc.nick, newNick)
872 me = true
873 uc.nick = newNick
874 uc.nickCM = uc.network.casemap(uc.nick)
875 }
876
877 for _, entry := range uc.channels.innerMap {
878 ch := entry.value.(*upstreamChannel)
879 memberships := ch.Members.Value(msg.Prefix.Name)
880 if memberships != nil {
881 ch.Members.Delete(msg.Prefix.Name)
882 ch.Members.SetValue(newNick, memberships)
883 uc.appendLog(ch.Name, msg)
884 }
885 }
886
887 if !me {
888 uc.forEachDownstream(func(dc *downstreamConn) {
889 dc.SendMessage(dc.marshalMessage(msg, uc.network))
890 })
891 } else {
892 uc.forEachDownstream(func(dc *downstreamConn) {
893 dc.updateNick()
894 })
895 uc.updateMonitor()
896 }
897 case "SETNAME":
898 if msg.Prefix == nil {
899 return fmt.Errorf("expected a prefix")
900 }
901
902 var newRealname string
903 if err := parseMessageParams(msg, &newRealname); err != nil {
904 return err
905 }
906
907 // TODO: consider appending this message to logs
908
909 if uc.isOurNick(msg.Prefix.Name) {
910 uc.logger.Printf("changed realname from %q to %q", uc.realname, newRealname)
911 uc.realname = newRealname
912
913 uc.forEachDownstream(func(dc *downstreamConn) {
914 dc.updateRealname()
915 })
916 } else {
917 uc.forEachDownstream(func(dc *downstreamConn) {
918 dc.SendMessage(dc.marshalMessage(msg, uc.network))
919 })
920 }
921 case "JOIN":
922 if msg.Prefix == nil {
923 return fmt.Errorf("expected a prefix")
924 }
925
926 var channels string
927 if err := parseMessageParams(msg, &channels); err != nil {
928 return err
929 }
930
931 for _, ch := range strings.Split(channels, ",") {
932 if uc.isOurNick(msg.Prefix.Name) {
933 uc.logger.Printf("joined channel %q", ch)
934 members := membershipsCasemapMap{newCasemapMap(0)}
935 members.casemap = uc.network.casemap
936 uc.channels.SetValue(ch, &upstreamChannel{
937 Name: ch,
938 conn: uc,
939 Members: members,
940 })
941 uc.updateChannelAutoDetach(ch)
942
943 uc.SendMessage(ctx, &irc.Message{
944 Command: "MODE",
945 Params: []string{ch},
946 })
947 } else {
948 ch, err := uc.getChannel(ch)
949 if err != nil {
950 return err
951 }
952 ch.Members.SetValue(msg.Prefix.Name, &memberships{})
953 }
954
955 chMsg := msg.Copy()
956 chMsg.Params[0] = ch
957 uc.produce(ch, chMsg, nil)
958 }
959 case "PART":
960 if msg.Prefix == nil {
961 return fmt.Errorf("expected a prefix")
962 }
963
964 var channels string
965 if err := parseMessageParams(msg, &channels); err != nil {
966 return err
967 }
968
969 for _, ch := range strings.Split(channels, ",") {
970 if uc.isOurNick(msg.Prefix.Name) {
971 uc.logger.Printf("parted channel %q", ch)
972 uch := uc.channels.Value(ch)
973 if uch != nil {
974 uc.channels.Delete(ch)
975 uch.updateAutoDetach(0)
976 }
977 } else {
978 ch, err := uc.getChannel(ch)
979 if err != nil {
980 return err
981 }
982 ch.Members.Delete(msg.Prefix.Name)
983 }
984
985 chMsg := msg.Copy()
986 chMsg.Params[0] = ch
987 uc.produce(ch, chMsg, nil)
988 }
989 case "KICK":
990 if msg.Prefix == nil {
991 return fmt.Errorf("expected a prefix")
992 }
993
994 var channel, user string
995 if err := parseMessageParams(msg, &channel, &user); err != nil {
996 return err
997 }
998
999 if uc.isOurNick(user) {
1000 uc.logger.Printf("kicked from channel %q by %s", channel, msg.Prefix.Name)
1001 uc.channels.Delete(channel)
1002 } else {
1003 ch, err := uc.getChannel(channel)
1004 if err != nil {
1005 return err
1006 }
1007 ch.Members.Delete(user)
1008 }
1009
1010 uc.produce(channel, msg, nil)
1011 case "QUIT":
1012 if msg.Prefix == nil {
1013 return fmt.Errorf("expected a prefix")
1014 }
1015
1016 if uc.isOurNick(msg.Prefix.Name) {
1017 uc.logger.Printf("quit")
1018 }
1019
1020 for _, entry := range uc.channels.innerMap {
1021 ch := entry.value.(*upstreamChannel)
1022 if ch.Members.Has(msg.Prefix.Name) {
1023 ch.Members.Delete(msg.Prefix.Name)
1024
1025 uc.appendLog(ch.Name, msg)
1026 }
1027 }
1028
1029 if msg.Prefix.Name != uc.nick {
1030 uc.forEachDownstream(func(dc *downstreamConn) {
1031 dc.SendMessage(dc.marshalMessage(msg, uc.network))
1032 })
1033 }
1034 case irc.RPL_TOPIC, irc.RPL_NOTOPIC:
1035 var name, topic string
1036 if err := parseMessageParams(msg, nil, &name, &topic); err != nil {
1037 return err
1038 }
1039 ch, err := uc.getChannel(name)
1040 if err != nil {
1041 return err
1042 }
1043 if msg.Command == irc.RPL_TOPIC {
1044 ch.Topic = topic
1045 } else {
1046 ch.Topic = ""
1047 }
1048 case "TOPIC":
1049 if msg.Prefix == nil {
1050 return fmt.Errorf("expected a prefix")
1051 }
1052
1053 var name string
1054 if err := parseMessageParams(msg, &name); err != nil {
1055 return err
1056 }
1057 ch, err := uc.getChannel(name)
1058 if err != nil {
1059 return err
1060 }
1061 if len(msg.Params) > 1 {
1062 ch.Topic = msg.Params[1]
1063 ch.TopicWho = msg.Prefix.Copy()
1064 ch.TopicTime = time.Now() // TODO use msg.Tags["time"]
1065 } else {
1066 ch.Topic = ""
1067 }
1068 uc.produce(ch.Name, msg, nil)
1069 case "MODE":
1070 var name, modeStr string
1071 if err := parseMessageParams(msg, &name, &modeStr); err != nil {
1072 return err
1073 }
1074
1075 if !uc.isChannel(name) { // user mode change
1076 if name != uc.nick {
1077 return fmt.Errorf("received MODE message for unknown nick %q", name)
1078 }
1079
1080 if err := uc.modes.Apply(modeStr); err != nil {
1081 return err
1082 }
1083
1084 uc.forEachDownstream(func(dc *downstreamConn) {
1085 if dc.upstream() == nil {
1086 return
1087 }
1088
1089 dc.SendMessage(msg)
1090 })
1091 } else { // channel mode change
1092 ch, err := uc.getChannel(name)
1093 if err != nil {
1094 return err
1095 }
1096
1097 needMarshaling, err := applyChannelModes(ch, modeStr, msg.Params[2:])
1098 if err != nil {
1099 return err
1100 }
1101
1102 uc.appendLog(ch.Name, msg)
1103
1104 c := uc.network.channels.Value(name)
1105 if c == nil || !c.Detached {
1106 uc.forEachDownstream(func(dc *downstreamConn) {
1107 params := make([]string, len(msg.Params))
1108 params[0] = dc.marshalEntity(uc.network, name)
1109 params[1] = modeStr
1110
1111 copy(params[2:], msg.Params[2:])
1112 for i, modeParam := range params[2:] {
1113 if _, ok := needMarshaling[i]; ok {
1114 params[2+i] = dc.marshalEntity(uc.network, modeParam)
1115 }
1116 }
1117
1118 dc.SendMessage(&irc.Message{
1119 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
1120 Command: "MODE",
1121 Params: params,
1122 })
1123 })
1124 }
1125 }
1126 case irc.RPL_UMODEIS:
1127 if err := parseMessageParams(msg, nil); err != nil {
1128 return err
1129 }
1130 modeStr := ""
1131 if len(msg.Params) > 1 {
1132 modeStr = msg.Params[1]
1133 }
1134
1135 uc.modes = ""
1136 if err := uc.modes.Apply(modeStr); err != nil {
1137 return err
1138 }
1139
1140 uc.forEachDownstream(func(dc *downstreamConn) {
1141 if dc.upstream() == nil {
1142 return
1143 }
1144
1145 dc.SendMessage(msg)
1146 })
1147 case irc.RPL_CHANNELMODEIS:
1148 var channel string
1149 if err := parseMessageParams(msg, nil, &channel); err != nil {
1150 return err
1151 }
1152 modeStr := ""
1153 if len(msg.Params) > 2 {
1154 modeStr = msg.Params[2]
1155 }
1156
1157 ch, err := uc.getChannel(channel)
1158 if err != nil {
1159 return err
1160 }
1161
1162 firstMode := ch.modes == nil
1163 ch.modes = make(map[byte]string)
1164 if _, err := applyChannelModes(ch, modeStr, msg.Params[3:]); err != nil {
1165 return err
1166 }
1167
1168 c := uc.network.channels.Value(channel)
1169 if firstMode && (c == nil || !c.Detached) {
1170 modeStr, modeParams := ch.modes.Format()
1171
1172 uc.forEachDownstream(func(dc *downstreamConn) {
1173 params := []string{dc.nick, dc.marshalEntity(uc.network, channel), modeStr}
1174 params = append(params, modeParams...)
1175
1176 dc.SendMessage(&irc.Message{
1177 Prefix: dc.srv.prefix(),
1178 Command: irc.RPL_CHANNELMODEIS,
1179 Params: params,
1180 })
1181 })
1182 }
1183 case rpl_creationtime:
1184 var channel, creationTime string
1185 if err := parseMessageParams(msg, nil, &channel, &creationTime); err != nil {
1186 return err
1187 }
1188
1189 ch, err := uc.getChannel(channel)
1190 if err != nil {
1191 return err
1192 }
1193
1194 firstCreationTime := ch.creationTime == ""
1195 ch.creationTime = creationTime
1196
1197 c := uc.network.channels.Value(channel)
1198 if firstCreationTime && (c == nil || !c.Detached) {
1199 uc.forEachDownstream(func(dc *downstreamConn) {
1200 dc.SendMessage(&irc.Message{
1201 Prefix: dc.srv.prefix(),
1202 Command: rpl_creationtime,
1203 Params: []string{dc.nick, dc.marshalEntity(uc.network, ch.Name), creationTime},
1204 })
1205 })
1206 }
1207 case rpl_topicwhotime:
1208 var channel, who, timeStr string
1209 if err := parseMessageParams(msg, nil, &channel, &who, &timeStr); err != nil {
1210 return err
1211 }
1212
1213 ch, err := uc.getChannel(channel)
1214 if err != nil {
1215 return err
1216 }
1217
1218 firstTopicWhoTime := ch.TopicWho == nil
1219 ch.TopicWho = irc.ParsePrefix(who)
1220 sec, err := strconv.ParseInt(timeStr, 10, 64)
1221 if err != nil {
1222 return fmt.Errorf("failed to parse topic time: %v", err)
1223 }
1224 ch.TopicTime = time.Unix(sec, 0)
1225
1226 c := uc.network.channels.Value(channel)
1227 if firstTopicWhoTime && (c == nil || !c.Detached) {
1228 uc.forEachDownstream(func(dc *downstreamConn) {
1229 topicWho := dc.marshalUserPrefix(uc.network, ch.TopicWho)
1230 dc.SendMessage(&irc.Message{
1231 Prefix: dc.srv.prefix(),
1232 Command: rpl_topicwhotime,
1233 Params: []string{
1234 dc.nick,
1235 dc.marshalEntity(uc.network, ch.Name),
1236 topicWho.String(),
1237 timeStr,
1238 },
1239 })
1240 })
1241 }
1242 case irc.RPL_LIST:
1243 var channel, clients, topic string
1244 if err := parseMessageParams(msg, nil, &channel, &clients, &topic); err != nil {
1245 return err
1246 }
1247
1248 dc, cmd := uc.currentPendingCommand("LIST")
1249 if cmd == nil {
1250 return fmt.Errorf("unexpected RPL_LIST: no matching pending LIST")
1251 } else if dc == nil {
1252 return nil
1253 }
1254
1255 dc.SendMessage(&irc.Message{
1256 Prefix: dc.srv.prefix(),
1257 Command: irc.RPL_LIST,
1258 Params: []string{dc.nick, dc.marshalEntity(uc.network, channel), clients, topic},
1259 })
1260 case irc.RPL_LISTEND:
1261 dc, cmd := uc.dequeueCommand("LIST")
1262 if cmd == nil {
1263 return fmt.Errorf("unexpected RPL_LISTEND: no matching pending LIST")
1264 } else if dc == nil {
1265 return nil
1266 }
1267
1268 dc.SendMessage(&irc.Message{
1269 Prefix: dc.srv.prefix(),
1270 Command: irc.RPL_LISTEND,
1271 Params: []string{dc.nick, "End of /LIST"},
1272 })
1273 case irc.RPL_NAMREPLY:
1274 var name, statusStr, members string
1275 if err := parseMessageParams(msg, nil, &statusStr, &name, &members); err != nil {
1276 return err
1277 }
1278
1279 ch := uc.channels.Value(name)
1280 if ch == nil {
1281 // NAMES on a channel we have not joined, forward to downstream
1282 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1283 channel := dc.marshalEntity(uc.network, name)
1284 members := splitSpace(members)
1285 for i, member := range members {
1286 memberships, nick := uc.parseMembershipPrefix(member)
1287 members[i] = memberships.Format(dc) + dc.marshalEntity(uc.network, nick)
1288 }
1289 memberStr := strings.Join(members, " ")
1290
1291 dc.SendMessage(&irc.Message{
1292 Prefix: dc.srv.prefix(),
1293 Command: irc.RPL_NAMREPLY,
1294 Params: []string{dc.nick, statusStr, channel, memberStr},
1295 })
1296 })
1297 return nil
1298 }
1299
1300 status, err := parseChannelStatus(statusStr)
1301 if err != nil {
1302 return err
1303 }
1304 ch.Status = status
1305
1306 for _, s := range splitSpace(members) {
1307 memberships, nick := uc.parseMembershipPrefix(s)
1308 ch.Members.SetValue(nick, memberships)
1309 }
1310 case irc.RPL_ENDOFNAMES:
1311 var name string
1312 if err := parseMessageParams(msg, nil, &name); err != nil {
1313 return err
1314 }
1315
1316 ch := uc.channels.Value(name)
1317 if ch == nil {
1318 // NAMES on a channel we have not joined, forward to downstream
1319 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1320 channel := dc.marshalEntity(uc.network, name)
1321
1322 dc.SendMessage(&irc.Message{
1323 Prefix: dc.srv.prefix(),
1324 Command: irc.RPL_ENDOFNAMES,
1325 Params: []string{dc.nick, channel, "End of /NAMES list"},
1326 })
1327 })
1328 return nil
1329 }
1330
1331 if ch.complete {
1332 return fmt.Errorf("received unexpected RPL_ENDOFNAMES")
1333 }
1334 ch.complete = true
1335
1336 c := uc.network.channels.Value(name)
1337 if c == nil || !c.Detached {
1338 uc.forEachDownstream(func(dc *downstreamConn) {
1339 forwardChannel(ctx, dc, ch)
1340 })
1341 }
1342 case irc.RPL_WHOREPLY:
1343 var channel, username, host, server, nick, flags, trailing string
1344 if err := parseMessageParams(msg, nil, &channel, &username, &host, &server, &nick, &flags, &trailing); err != nil {
1345 return err
1346 }
1347
1348 dc, cmd := uc.currentPendingCommand("WHO")
1349 if cmd == nil {
1350 return fmt.Errorf("unexpected RPL_WHOREPLY: no matching pending WHO")
1351 } else if dc == nil {
1352 return nil
1353 }
1354
1355 if channel != "*" {
1356 channel = dc.marshalEntity(uc.network, channel)
1357 }
1358 nick = dc.marshalEntity(uc.network, nick)
1359 dc.SendMessage(&irc.Message{
1360 Prefix: dc.srv.prefix(),
1361 Command: irc.RPL_WHOREPLY,
1362 Params: []string{dc.nick, channel, username, host, server, nick, flags, trailing},
1363 })
1364 case rpl_whospcrpl:
1365 dc, cmd := uc.currentPendingCommand("WHO")
1366 if cmd == nil {
1367 return fmt.Errorf("unexpected RPL_WHOSPCRPL: no matching pending WHO")
1368 } else if dc == nil {
1369 return nil
1370 }
1371
1372 // Only supported in single-upstream mode, so forward as-is
1373 dc.SendMessage(msg)
1374 case irc.RPL_ENDOFWHO:
1375 var name string
1376 if err := parseMessageParams(msg, nil, &name); err != nil {
1377 return err
1378 }
1379
1380 dc, cmd := uc.dequeueCommand("WHO")
1381 if cmd == nil {
1382 return fmt.Errorf("unexpected RPL_ENDOFWHO: no matching pending WHO")
1383 } else if dc == nil {
1384 return nil
1385 }
1386
1387 mask := "*"
1388 if len(cmd.Params) > 0 {
1389 mask = cmd.Params[0]
1390 }
1391 dc.SendMessage(&irc.Message{
1392 Prefix: dc.srv.prefix(),
1393 Command: irc.RPL_ENDOFWHO,
1394 Params: []string{dc.nick, mask, "End of /WHO list"},
1395 })
1396 case irc.RPL_WHOISUSER:
1397 var nick, username, host, realname string
1398 if err := parseMessageParams(msg, nil, &nick, &username, &host, nil, &realname); err != nil {
1399 return err
1400 }
1401
1402 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1403 nick := dc.marshalEntity(uc.network, nick)
1404 dc.SendMessage(&irc.Message{
1405 Prefix: dc.srv.prefix(),
1406 Command: irc.RPL_WHOISUSER,
1407 Params: []string{dc.nick, nick, username, host, "*", realname},
1408 })
1409 })
1410 case irc.RPL_WHOISSERVER:
1411 var nick, server, serverInfo string
1412 if err := parseMessageParams(msg, nil, &nick, &server, &serverInfo); err != nil {
1413 return err
1414 }
1415
1416 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1417 nick := dc.marshalEntity(uc.network, nick)
1418 dc.SendMessage(&irc.Message{
1419 Prefix: dc.srv.prefix(),
1420 Command: irc.RPL_WHOISSERVER,
1421 Params: []string{dc.nick, nick, server, serverInfo},
1422 })
1423 })
1424 case irc.RPL_WHOISOPERATOR:
1425 var nick string
1426 if err := parseMessageParams(msg, nil, &nick); err != nil {
1427 return err
1428 }
1429
1430 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1431 nick := dc.marshalEntity(uc.network, nick)
1432 dc.SendMessage(&irc.Message{
1433 Prefix: dc.srv.prefix(),
1434 Command: irc.RPL_WHOISOPERATOR,
1435 Params: []string{dc.nick, nick, "is an IRC operator"},
1436 })
1437 })
1438 case irc.RPL_WHOISIDLE:
1439 var nick string
1440 if err := parseMessageParams(msg, nil, &nick, nil); err != nil {
1441 return err
1442 }
1443
1444 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1445 nick := dc.marshalEntity(uc.network, nick)
1446 params := []string{dc.nick, nick}
1447 params = append(params, msg.Params[2:]...)
1448 dc.SendMessage(&irc.Message{
1449 Prefix: dc.srv.prefix(),
1450 Command: irc.RPL_WHOISIDLE,
1451 Params: params,
1452 })
1453 })
1454 case irc.RPL_WHOISCHANNELS:
1455 var nick, channelList string
1456 if err := parseMessageParams(msg, nil, &nick, &channelList); err != nil {
1457 return err
1458 }
1459 channels := splitSpace(channelList)
1460
1461 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1462 nick := dc.marshalEntity(uc.network, nick)
1463 channelList := make([]string, len(channels))
1464 for i, channel := range channels {
1465 prefix, channel := uc.parseMembershipPrefix(channel)
1466 channel = dc.marshalEntity(uc.network, channel)
1467 channelList[i] = prefix.Format(dc) + channel
1468 }
1469 channels := strings.Join(channelList, " ")
1470 dc.SendMessage(&irc.Message{
1471 Prefix: dc.srv.prefix(),
1472 Command: irc.RPL_WHOISCHANNELS,
1473 Params: []string{dc.nick, nick, channels},
1474 })
1475 })
1476 case irc.RPL_ENDOFWHOIS:
1477 var nick string
1478 if err := parseMessageParams(msg, nil, &nick); err != nil {
1479 return err
1480 }
1481
1482 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1483 nick := dc.marshalEntity(uc.network, nick)
1484 dc.SendMessage(&irc.Message{
1485 Prefix: dc.srv.prefix(),
1486 Command: irc.RPL_ENDOFWHOIS,
1487 Params: []string{dc.nick, nick, "End of /WHOIS list"},
1488 })
1489 })
1490 case "INVITE":
1491 var nick, channel string
1492 if err := parseMessageParams(msg, &nick, &channel); err != nil {
1493 return err
1494 }
1495
1496 weAreInvited := uc.isOurNick(nick)
1497
1498 uc.forEachDownstream(func(dc *downstreamConn) {
1499 if !weAreInvited && !dc.caps["invite-notify"] {
1500 return
1501 }
1502 dc.SendMessage(&irc.Message{
1503 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
1504 Command: "INVITE",
1505 Params: []string{dc.marshalEntity(uc.network, nick), dc.marshalEntity(uc.network, channel)},
1506 })
1507 })
1508 case irc.RPL_INVITING:
1509 var nick, channel string
1510 if err := parseMessageParams(msg, nil, &nick, &channel); err != nil {
1511 return err
1512 }
1513
1514 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1515 dc.SendMessage(&irc.Message{
1516 Prefix: dc.srv.prefix(),
1517 Command: irc.RPL_INVITING,
1518 Params: []string{dc.nick, dc.marshalEntity(uc.network, nick), dc.marshalEntity(uc.network, channel)},
1519 })
1520 })
1521 case irc.RPL_MONONLINE, irc.RPL_MONOFFLINE:
1522 var targetsStr string
1523 if err := parseMessageParams(msg, nil, &targetsStr); err != nil {
1524 return err
1525 }
1526 targets := strings.Split(targetsStr, ",")
1527
1528 online := msg.Command == irc.RPL_MONONLINE
1529 for _, target := range targets {
1530 prefix := irc.ParsePrefix(target)
1531 uc.monitored.SetValue(prefix.Name, online)
1532 }
1533
1534 // Check if the nick we want is now free
1535 wantNick := GetNick(&uc.user.User, &uc.network.Network)
1536 wantNickCM := uc.network.casemap(wantNick)
1537 if !online && uc.nickCM != wantNickCM {
1538 found := false
1539 for _, target := range targets {
1540 prefix := irc.ParsePrefix(target)
1541 if uc.network.casemap(prefix.Name) == wantNickCM {
1542 found = true
1543 break
1544 }
1545 }
1546 if found {
1547 uc.logger.Printf("desired nick %q is now available", wantNick)
1548 uc.SendMessage(ctx, &irc.Message{
1549 Command: "NICK",
1550 Params: []string{wantNick},
1551 })
1552 }
1553 }
1554
1555 uc.forEachDownstream(func(dc *downstreamConn) {
1556 for _, target := range targets {
1557 prefix := irc.ParsePrefix(target)
1558 if dc.monitored.Has(prefix.Name) {
1559 dc.SendMessage(&irc.Message{
1560 Prefix: dc.srv.prefix(),
1561 Command: msg.Command,
1562 Params: []string{dc.nick, target},
1563 })
1564 }
1565 }
1566 })
1567 case irc.ERR_MONLISTFULL:
1568 var limit, targetsStr string
1569 if err := parseMessageParams(msg, nil, &limit, &targetsStr); err != nil {
1570 return err
1571 }
1572
1573 targets := strings.Split(targetsStr, ",")
1574 uc.forEachDownstream(func(dc *downstreamConn) {
1575 for _, target := range targets {
1576 if dc.monitored.Has(target) {
1577 dc.SendMessage(&irc.Message{
1578 Prefix: dc.srv.prefix(),
1579 Command: msg.Command,
1580 Params: []string{dc.nick, limit, target},
1581 })
1582 }
1583 }
1584 })
1585 case irc.RPL_AWAY:
1586 var nick, reason string
1587 if err := parseMessageParams(msg, nil, &nick, &reason); err != nil {
1588 return err
1589 }
1590
1591 uc.forEachDownstream(func(dc *downstreamConn) {
1592 dc.SendMessage(&irc.Message{
1593 Prefix: dc.srv.prefix(),
1594 Command: irc.RPL_AWAY,
1595 Params: []string{dc.nick, dc.marshalEntity(uc.network, nick), reason},
1596 })
1597 })
1598 case "AWAY", "ACCOUNT":
1599 if msg.Prefix == nil {
1600 return fmt.Errorf("expected a prefix")
1601 }
1602
1603 uc.forEachDownstream(func(dc *downstreamConn) {
1604 dc.SendMessage(&irc.Message{
1605 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
1606 Command: msg.Command,
1607 Params: msg.Params,
1608 })
1609 })
1610 case irc.RPL_BANLIST, irc.RPL_INVITELIST, irc.RPL_EXCEPTLIST:
1611 var channel, mask string
1612 if err := parseMessageParams(msg, nil, &channel, &mask); err != nil {
1613 return err
1614 }
1615 var addNick, addTime string
1616 if len(msg.Params) >= 5 {
1617 addNick = msg.Params[3]
1618 addTime = msg.Params[4]
1619 }
1620
1621 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1622 channel := dc.marshalEntity(uc.network, channel)
1623
1624 var params []string
1625 if addNick != "" && addTime != "" {
1626 addNick := dc.marshalEntity(uc.network, addNick)
1627 params = []string{dc.nick, channel, mask, addNick, addTime}
1628 } else {
1629 params = []string{dc.nick, channel, mask}
1630 }
1631
1632 dc.SendMessage(&irc.Message{
1633 Prefix: dc.srv.prefix(),
1634 Command: msg.Command,
1635 Params: params,
1636 })
1637 })
1638 case irc.RPL_ENDOFBANLIST, irc.RPL_ENDOFINVITELIST, irc.RPL_ENDOFEXCEPTLIST:
1639 var channel, trailing string
1640 if err := parseMessageParams(msg, nil, &channel, &trailing); err != nil {
1641 return err
1642 }
1643
1644 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1645 upstreamChannel := dc.marshalEntity(uc.network, channel)
1646 dc.SendMessage(&irc.Message{
1647 Prefix: dc.srv.prefix(),
1648 Command: msg.Command,
1649 Params: []string{dc.nick, upstreamChannel, trailing},
1650 })
1651 })
1652 case irc.ERR_UNKNOWNCOMMAND, irc.RPL_TRYAGAIN:
1653 var command, reason string
1654 if err := parseMessageParams(msg, nil, &command, &reason); err != nil {
1655 return err
1656 }
1657
1658 if dc, _ := uc.dequeueCommand(command); dc != nil && downstreamID == 0 {
1659 downstreamID = dc.id
1660 }
1661
1662 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1663 dc.SendMessage(&irc.Message{
1664 Prefix: uc.srv.prefix(),
1665 Command: msg.Command,
1666 Params: []string{dc.nick, command, reason},
1667 })
1668 })
1669 case "FAIL":
1670 var command, code string
1671 if err := parseMessageParams(msg, &command, &code); err != nil {
1672 return err
1673 }
1674
1675 if !uc.registered && command == "*" && code == "ACCOUNT_REQUIRED" {
1676 return registrationError{msg}
1677 }
1678
1679 if dc, _ := uc.dequeueCommand(command); dc != nil && downstreamID == 0 {
1680 downstreamID = dc.id
1681 }
1682
1683 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1684 dc.SendMessage(msg)
1685 })
1686 case "ACK":
1687 // Ignore
1688 case irc.RPL_NOWAWAY, irc.RPL_UNAWAY:
1689 // Ignore
1690 case irc.RPL_YOURHOST, irc.RPL_CREATED:
1691 // Ignore
1692 case irc.RPL_LUSERCLIENT, irc.RPL_LUSEROP, irc.RPL_LUSERUNKNOWN, irc.RPL_LUSERCHANNELS, irc.RPL_LUSERME:
1693 fallthrough
1694 case irc.RPL_STATSVLINE, rpl_statsping, irc.RPL_STATSBLINE, irc.RPL_STATSDLINE:
1695 fallthrough
1696 case rpl_localusers, rpl_globalusers:
1697 fallthrough
1698 case irc.RPL_MOTDSTART, irc.RPL_MOTD:
1699 // Ignore these messages if they're part of the initial registration
1700 // message burst. Forward them if the user explicitly asked for them.
1701 if !uc.gotMotd {
1702 return nil
1703 }
1704
1705 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1706 dc.SendMessage(&irc.Message{
1707 Prefix: uc.srv.prefix(),
1708 Command: msg.Command,
1709 Params: msg.Params,
1710 })
1711 })
1712 case irc.RPL_LISTSTART:
1713 // Ignore
1714 case "ERROR":
1715 var text string
1716 if err := parseMessageParams(msg, &text); err != nil {
1717 return err
1718 }
1719 return fmt.Errorf("fatal server error: %v", text)
1720 case irc.ERR_NICKNAMEINUSE:
1721 // At this point, we haven't received ISUPPORT so we don't know the
1722 // maximum nickname length or whether the server supports MONITOR. Many
1723 // servers have NICKLEN=30 so let's just use that.
1724 if !uc.registered && len(uc.nick)+1 < 30 {
1725 uc.nick = uc.nick + "_"
1726 uc.nickCM = uc.network.casemap(uc.nick)
1727 uc.logger.Printf("desired nick is not available, falling back to %q", uc.nick)
1728 uc.SendMessage(ctx, &irc.Message{
1729 Command: "NICK",
1730 Params: []string{uc.nick},
1731 })
1732 return nil
1733 }
1734 fallthrough
1735 case irc.ERR_PASSWDMISMATCH, irc.ERR_ERRONEUSNICKNAME, irc.ERR_NICKCOLLISION, irc.ERR_UNAVAILRESOURCE, irc.ERR_NOPERMFORHOST, irc.ERR_YOUREBANNEDCREEP:
1736 if !uc.registered {
1737 return registrationError{msg}
1738 }
1739 fallthrough
1740 default:
1741 uc.logger.Printf("unhandled message: %v", msg)
1742
1743 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1744 // best effort marshaling for unknown messages, replies and errors:
1745 // most numerics start with the user nick, marshal it if that's the case
1746 // otherwise, conservately keep the params without marshaling
1747 params := msg.Params
1748 if _, err := strconv.Atoi(msg.Command); err == nil { // numeric
1749 if len(msg.Params) > 0 && isOurNick(uc.network, msg.Params[0]) {
1750 params[0] = dc.nick
1751 }
1752 }
1753 dc.SendMessage(&irc.Message{
1754 Prefix: uc.srv.prefix(),
1755 Command: msg.Command,
1756 Params: params,
1757 })
1758 })
1759 }
1760 return nil
1761}
1762
1763func (uc *upstreamConn) handleDetachedMessage(ctx context.Context, ch *Channel, msg *irc.Message) {
1764 if uc.network.detachedMessageNeedsRelay(ch, msg) {
1765 uc.forEachDownstream(func(dc *downstreamConn) {
1766 dc.relayDetachedMessage(uc.network, msg)
1767 })
1768 }
1769 if ch.ReattachOn == FilterMessage || (ch.ReattachOn == FilterHighlight && uc.network.isHighlight(msg)) {
1770 uc.network.attach(ctx, ch)
1771 if err := uc.srv.db.StoreChannel(ctx, uc.network.ID, ch); err != nil {
1772 uc.logger.Printf("failed to update channel %q: %v", ch.Name, err)
1773 }
1774 }
1775}
1776
1777func (uc *upstreamConn) handleChanModes(s string) error {
1778 parts := strings.SplitN(s, ",", 5)
1779 if len(parts) < 4 {
1780 return fmt.Errorf("malformed ISUPPORT CHANMODES value: %v", s)
1781 }
1782 modes := make(map[byte]channelModeType)
1783 for i, mt := range []channelModeType{modeTypeA, modeTypeB, modeTypeC, modeTypeD} {
1784 for j := 0; j < len(parts[i]); j++ {
1785 mode := parts[i][j]
1786 modes[mode] = mt
1787 }
1788 }
1789 uc.availableChannelModes = modes
1790 return nil
1791}
1792
1793func (uc *upstreamConn) handleMemberships(s string) error {
1794 if s == "" {
1795 uc.availableMemberships = nil
1796 return nil
1797 }
1798
1799 if s[0] != '(' {
1800 return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", s)
1801 }
1802 sep := strings.IndexByte(s, ')')
1803 if sep < 0 || len(s) != sep*2 {
1804 return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", s)
1805 }
1806 memberships := make([]membership, len(s)/2-1)
1807 for i := range memberships {
1808 memberships[i] = membership{
1809 Mode: s[i+1],
1810 Prefix: s[sep+i+1],
1811 }
1812 }
1813 uc.availableMemberships = memberships
1814 return nil
1815}
1816
1817func (uc *upstreamConn) handleSupportedCaps(capsStr string) {
1818 caps := strings.Fields(capsStr)
1819 for _, s := range caps {
1820 kv := strings.SplitN(s, "=", 2)
1821 k := strings.ToLower(kv[0])
1822 var v string
1823 if len(kv) == 2 {
1824 v = kv[1]
1825 }
1826 uc.supportedCaps[k] = v
1827 }
1828}
1829
1830func (uc *upstreamConn) requestCaps() {
1831 var requestCaps []string
1832 for c := range permanentUpstreamCaps {
1833 if _, ok := uc.supportedCaps[c]; ok && !uc.caps[c] {
1834 requestCaps = append(requestCaps, c)
1835 }
1836 }
1837
1838 if len(requestCaps) == 0 {
1839 return
1840 }
1841
1842 uc.SendMessage(context.TODO(), &irc.Message{
1843 Command: "CAP",
1844 Params: []string{"REQ", strings.Join(requestCaps, " ")},
1845 })
1846}
1847
1848func (uc *upstreamConn) supportsSASL(mech string) bool {
1849 v, ok := uc.supportedCaps["sasl"]
1850 if !ok {
1851 return false
1852 }
1853
1854 if v == "" {
1855 return true
1856 }
1857
1858 mechanisms := strings.Split(v, ",")
1859 for _, mech := range mechanisms {
1860 if strings.EqualFold(mech, mech) {
1861 return true
1862 }
1863 }
1864 return false
1865}
1866
1867func (uc *upstreamConn) requestSASL() bool {
1868 if uc.network.SASL.Mechanism == "" {
1869 return false
1870 }
1871 return uc.supportsSASL(uc.network.SASL.Mechanism)
1872}
1873
1874func (uc *upstreamConn) handleCapAck(ctx context.Context, name string, ok bool) error {
1875 uc.caps[name] = ok
1876
1877 switch name {
1878 case "sasl":
1879 if !uc.requestSASL() {
1880 return nil
1881 }
1882 if !ok {
1883 uc.logger.Printf("server refused to acknowledge the SASL capability")
1884 return nil
1885 }
1886
1887 auth := &uc.network.SASL
1888 switch auth.Mechanism {
1889 case "PLAIN":
1890 uc.logger.Printf("starting SASL PLAIN authentication with username %q", auth.Plain.Username)
1891 uc.saslClient = sasl.NewPlainClient("", auth.Plain.Username, auth.Plain.Password)
1892 case "EXTERNAL":
1893 uc.logger.Printf("starting SASL EXTERNAL authentication")
1894 uc.saslClient = sasl.NewExternalClient("")
1895 default:
1896 return fmt.Errorf("unsupported SASL mechanism %q", name)
1897 }
1898
1899 uc.SendMessage(ctx, &irc.Message{
1900 Command: "AUTHENTICATE",
1901 Params: []string{auth.Mechanism},
1902 })
1903 default:
1904 if permanentUpstreamCaps[name] {
1905 break
1906 }
1907 uc.logger.Printf("received CAP ACK/NAK for a cap we don't support: %v", name)
1908 }
1909 return nil
1910}
1911
1912func splitSpace(s string) []string {
1913 return strings.FieldsFunc(s, func(r rune) bool {
1914 return r == ' '
1915 })
1916}
1917
1918func (uc *upstreamConn) register(ctx context.Context) {
1919 uc.nick = GetNick(&uc.user.User, &uc.network.Network)
1920 uc.nickCM = uc.network.casemap(uc.nick)
1921 uc.username = GetUsername(&uc.user.User, &uc.network.Network)
1922 uc.realname = GetRealname(&uc.user.User, &uc.network.Network)
1923
1924 uc.SendMessage(ctx, &irc.Message{
1925 Command: "CAP",
1926 Params: []string{"LS", "302"},
1927 })
1928
1929 if uc.network.Pass != "" {
1930 uc.SendMessage(ctx, &irc.Message{
1931 Command: "PASS",
1932 Params: []string{uc.network.Pass},
1933 })
1934 }
1935
1936 uc.SendMessage(ctx, &irc.Message{
1937 Command: "NICK",
1938 Params: []string{uc.nick},
1939 })
1940 uc.SendMessage(ctx, &irc.Message{
1941 Command: "USER",
1942 Params: []string{uc.username, "0", "*", uc.realname},
1943 })
1944}
1945
1946func (uc *upstreamConn) ReadMessage() (*irc.Message, error) {
1947 msg, err := uc.conn.ReadMessage()
1948 if err != nil {
1949 return nil, err
1950 }
1951 uc.srv.metrics.upstreamInMessagesTotal.Inc()
1952 return msg, nil
1953}
1954
1955func (uc *upstreamConn) runUntilRegistered(ctx context.Context) error {
1956 for !uc.registered {
1957 msg, err := uc.ReadMessage()
1958 if err != nil {
1959 return fmt.Errorf("failed to read message: %v", err)
1960 }
1961
1962 if err := uc.handleMessage(ctx, msg); err != nil {
1963 if _, ok := err.(registrationError); ok {
1964 return err
1965 } else {
1966 msg.Tags = nil // prevent message tags from cluttering logs
1967 return fmt.Errorf("failed to handle message %q: %v", msg, err)
1968 }
1969 }
1970 }
1971
1972 for _, command := range uc.network.ConnectCommands {
1973 m, err := irc.ParseMessage(command)
1974 if err != nil {
1975 uc.logger.Printf("failed to parse connect command %q: %v", command, err)
1976 } else {
1977 uc.SendMessage(ctx, m)
1978 }
1979 }
1980
1981 return nil
1982}
1983
1984func (uc *upstreamConn) readMessages(ch chan<- event) error {
1985 for {
1986 msg, err := uc.ReadMessage()
1987 if errors.Is(err, io.EOF) {
1988 break
1989 } else if err != nil {
1990 return fmt.Errorf("failed to read IRC command: %v", err)
1991 }
1992
1993 ch <- eventUpstreamMessage{msg, uc}
1994 }
1995
1996 return nil
1997}
1998
1999func (uc *upstreamConn) SendMessage(ctx context.Context, msg *irc.Message) {
2000 if !uc.caps["message-tags"] {
2001 msg = msg.Copy()
2002 msg.Tags = nil
2003 }
2004
2005 uc.srv.metrics.upstreamOutMessagesTotal.Inc()
2006 uc.conn.SendMessage(ctx, msg)
2007}
2008
2009func (uc *upstreamConn) SendMessageLabeled(ctx context.Context, downstreamID uint64, msg *irc.Message) {
2010 if uc.caps["labeled-response"] {
2011 if msg.Tags == nil {
2012 msg.Tags = make(map[string]irc.TagValue)
2013 }
2014 msg.Tags["label"] = irc.TagValue(fmt.Sprintf("sd-%d-%d", downstreamID, uc.nextLabelID))
2015 uc.nextLabelID++
2016 }
2017 uc.SendMessage(ctx, msg)
2018}
2019
2020// appendLog appends a message to the log file.
2021//
2022// The internal message ID is returned. If the message isn't recorded in the
2023// log file, an empty string is returned.
2024func (uc *upstreamConn) appendLog(entity string, msg *irc.Message) (msgID string) {
2025 if uc.user.msgStore == nil {
2026 return ""
2027 }
2028
2029 // Don't store messages with a server mask target
2030 if strings.HasPrefix(entity, "$") {
2031 return ""
2032 }
2033
2034 entityCM := uc.network.casemap(entity)
2035 if entityCM == "nickserv" {
2036 // The messages sent/received from NickServ may contain
2037 // security-related information (like passwords). Don't store these.
2038 return ""
2039 }
2040
2041 if !uc.network.delivered.HasTarget(entity) {
2042 // This is the first message we receive from this target. Save the last
2043 // message ID in delivery receipts, so that we can send the new message
2044 // in the backlog if an offline client reconnects.
2045 lastID, err := uc.user.msgStore.LastMsgID(&uc.network.Network, entityCM, time.Now())
2046 if err != nil {
2047 uc.logger.Printf("failed to log message: failed to get last message ID: %v", err)
2048 return ""
2049 }
2050
2051 uc.network.delivered.ForEachClient(func(clientName string) {
2052 uc.network.delivered.StoreID(entity, clientName, lastID)
2053 })
2054 }
2055
2056 msgID, err := uc.user.msgStore.Append(&uc.network.Network, entityCM, msg)
2057 if err != nil {
2058 uc.logger.Printf("failed to append message to store: %v", err)
2059 return ""
2060 }
2061
2062 return msgID
2063}
2064
2065// produce appends a message to the logs and forwards it to connected downstream
2066// connections.
2067//
2068// If origin is not nil and origin doesn't support echo-message, the message is
2069// forwarded to all connections except origin.
2070func (uc *upstreamConn) produce(target string, msg *irc.Message, origin *downstreamConn) {
2071 var msgID string
2072 if target != "" {
2073 msgID = uc.appendLog(target, msg)
2074 }
2075
2076 // Don't forward messages if it's a detached channel
2077 ch := uc.network.channels.Value(target)
2078 detached := ch != nil && ch.Detached
2079
2080 uc.forEachDownstream(func(dc *downstreamConn) {
2081 if !detached && (dc != origin || dc.caps["echo-message"]) {
2082 dc.sendMessageWithID(dc.marshalMessage(msg, uc.network), msgID)
2083 } else {
2084 dc.advanceMessageWithID(msg, msgID)
2085 }
2086 })
2087}
2088
2089func (uc *upstreamConn) updateAway() {
2090 ctx := context.TODO()
2091
2092 away := true
2093 uc.forEachDownstream(func(*downstreamConn) {
2094 away = false
2095 })
2096 if away == uc.away {
2097 return
2098 }
2099 if away {
2100 uc.SendMessage(ctx, &irc.Message{
2101 Command: "AWAY",
2102 Params: []string{"Auto away"},
2103 })
2104 } else {
2105 uc.SendMessage(ctx, &irc.Message{
2106 Command: "AWAY",
2107 })
2108 }
2109 uc.away = away
2110}
2111
2112func (uc *upstreamConn) updateChannelAutoDetach(name string) {
2113 uch := uc.channels.Value(name)
2114 if uch == nil {
2115 return
2116 }
2117 ch := uc.network.channels.Value(name)
2118 if ch == nil || ch.Detached {
2119 return
2120 }
2121 uch.updateAutoDetach(ch.DetachAfter)
2122}
2123
2124func (uc *upstreamConn) updateMonitor() {
2125 if _, ok := uc.isupport["MONITOR"]; !ok {
2126 return
2127 }
2128
2129 ctx := context.TODO()
2130
2131 add := make(map[string]struct{})
2132 var addList []string
2133 seen := make(map[string]struct{})
2134 uc.forEachDownstream(func(dc *downstreamConn) {
2135 for targetCM := range dc.monitored.innerMap {
2136 if !uc.monitored.Has(targetCM) {
2137 if _, ok := add[targetCM]; !ok {
2138 addList = append(addList, targetCM)
2139 add[targetCM] = struct{}{}
2140 }
2141 } else {
2142 seen[targetCM] = struct{}{}
2143 }
2144 }
2145 })
2146
2147 wantNick := GetNick(&uc.user.User, &uc.network.Network)
2148 wantNickCM := uc.network.casemap(wantNick)
2149 if _, ok := add[wantNickCM]; !ok && !uc.monitored.Has(wantNick) && !uc.isOurNick(wantNick) {
2150 addList = append(addList, wantNickCM)
2151 add[wantNickCM] = struct{}{}
2152 }
2153
2154 removeAll := true
2155 var removeList []string
2156 for targetCM, entry := range uc.monitored.innerMap {
2157 if _, ok := seen[targetCM]; ok {
2158 removeAll = false
2159 } else {
2160 removeList = append(removeList, entry.originalKey)
2161 }
2162 }
2163
2164 // TODO: better handle the case where len(uc.monitored) + len(addList)
2165 // exceeds the limit, probably by immediately sending ERR_MONLISTFULL?
2166
2167 if removeAll && len(addList) == 0 && len(removeList) > 0 {
2168 // Optimization when the last MONITOR-aware downstream disconnects
2169 uc.SendMessage(ctx, &irc.Message{
2170 Command: "MONITOR",
2171 Params: []string{"C"},
2172 })
2173 } else {
2174 msgs := generateMonitor("-", removeList)
2175 msgs = append(msgs, generateMonitor("+", addList)...)
2176 for _, msg := range msgs {
2177 uc.SendMessage(ctx, msg)
2178 }
2179 }
2180
2181 for _, target := range removeList {
2182 uc.monitored.Delete(target)
2183 }
2184}
Note: See TracBrowser for help on using the repository browser.