source: code/trunk/upstream.go@ 759

Last change on this file since 759 was 759, checked in by contact, 3 years ago

Block RPL_{CREATIONTIME,TOPICWHOTIME} for detached channels

Closes: https://todo.sr.ht/~emersion/soju/132

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