source: code/trunk/upstream.go@ 743

Last change on this file since 743 was 743, checked in by contact, 4 years ago

Fallback to alt nick

If the nickname we want is taken, fallback to another one by
appending underscores. Use MONITOR to figure out when we can request
our desired nick again.

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

File size: 55.3 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) endPendingCommands() {
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, "End of /LIST"},
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, "End of /WHO"},
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(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(&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(&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(&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(&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(&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(&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(&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 uc.registered = true
694 uc.logger.Printf("connection registered")
695
696 if uc.network.channels.Len() > 0 {
697 var channels, keys []string
698 for _, entry := range uc.network.channels.innerMap {
699 ch := entry.value.(*Channel)
700 channels = append(channels, ch.Name)
701 keys = append(keys, ch.Key)
702 }
703
704 for _, msg := range join(channels, keys) {
705 uc.SendMessage(msg)
706 }
707 }
708 case irc.RPL_MYINFO:
709 if err := parseMessageParams(msg, nil, &uc.serverName, nil, &uc.availableUserModes, nil); err != nil {
710 return err
711 }
712 case irc.RPL_ISUPPORT:
713 if err := parseMessageParams(msg, nil, nil); err != nil {
714 return err
715 }
716
717 var downstreamIsupport []string
718 for _, token := range msg.Params[1 : len(msg.Params)-1] {
719 parameter := token
720 var negate, hasValue bool
721 var value string
722 if strings.HasPrefix(token, "-") {
723 negate = true
724 token = token[1:]
725 } else if i := strings.IndexByte(token, '='); i >= 0 {
726 parameter = token[:i]
727 value = token[i+1:]
728 hasValue = true
729 }
730
731 if hasValue {
732 uc.isupport[parameter] = &value
733 } else if !negate {
734 uc.isupport[parameter] = nil
735 } else {
736 delete(uc.isupport, parameter)
737 }
738
739 var err error
740 switch parameter {
741 case "CASEMAPPING":
742 casemap, ok := parseCasemappingToken(value)
743 if !ok {
744 casemap = casemapRFC1459
745 }
746 uc.network.updateCasemapping(casemap)
747 uc.nickCM = uc.network.casemap(uc.nick)
748 uc.casemapIsSet = true
749 case "CHANMODES":
750 if !negate {
751 err = uc.handleChanModes(value)
752 } else {
753 uc.availableChannelModes = stdChannelModes
754 }
755 case "CHANTYPES":
756 if !negate {
757 uc.availableChannelTypes = value
758 } else {
759 uc.availableChannelTypes = stdChannelTypes
760 }
761 case "PREFIX":
762 if !negate {
763 err = uc.handleMemberships(value)
764 } else {
765 uc.availableMemberships = stdMemberships
766 }
767 }
768 if err != nil {
769 return err
770 }
771
772 if passthroughIsupport[parameter] {
773 downstreamIsupport = append(downstreamIsupport, token)
774 }
775 }
776
777 uc.updateMonitor()
778
779 uc.forEachDownstream(func(dc *downstreamConn) {
780 if dc.network == nil {
781 return
782 }
783 msgs := generateIsupport(dc.srv.prefix(), dc.nick, downstreamIsupport)
784 for _, msg := range msgs {
785 dc.SendMessage(msg)
786 }
787 })
788 case irc.ERR_NOMOTD, irc.RPL_ENDOFMOTD:
789 if !uc.casemapIsSet {
790 // upstream did not send any CASEMAPPING token, thus
791 // we assume it implements the old RFCs with rfc1459.
792 uc.casemapIsSet = true
793 uc.network.updateCasemapping(casemapRFC1459)
794 uc.nickCM = uc.network.casemap(uc.nick)
795 }
796
797 if !uc.gotMotd {
798 // Ignore the initial MOTD upon connection, but forward
799 // subsequent MOTD messages downstream
800 uc.gotMotd = true
801 return nil
802 }
803
804 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
805 dc.SendMessage(&irc.Message{
806 Prefix: uc.srv.prefix(),
807 Command: msg.Command,
808 Params: msg.Params,
809 })
810 })
811 case "BATCH":
812 var tag string
813 if err := parseMessageParams(msg, &tag); err != nil {
814 return err
815 }
816
817 if strings.HasPrefix(tag, "+") {
818 tag = tag[1:]
819 if _, ok := uc.batches[tag]; ok {
820 return fmt.Errorf("unexpected BATCH reference tag: batch was already defined: %q", tag)
821 }
822 var batchType string
823 if err := parseMessageParams(msg, nil, &batchType); err != nil {
824 return err
825 }
826 label := label
827 if label == "" && msgBatch != nil {
828 label = msgBatch.Label
829 }
830 uc.batches[tag] = batch{
831 Type: batchType,
832 Params: msg.Params[2:],
833 Outer: msgBatch,
834 Label: label,
835 }
836 } else if strings.HasPrefix(tag, "-") {
837 tag = tag[1:]
838 if _, ok := uc.batches[tag]; !ok {
839 return fmt.Errorf("unknown BATCH reference tag: %q", tag)
840 }
841 delete(uc.batches, tag)
842 } else {
843 return fmt.Errorf("unexpected BATCH reference tag: missing +/- prefix: %q", tag)
844 }
845 case "NICK":
846 if msg.Prefix == nil {
847 return fmt.Errorf("expected a prefix")
848 }
849
850 var newNick string
851 if err := parseMessageParams(msg, &newNick); err != nil {
852 return err
853 }
854
855 me := false
856 if uc.isOurNick(msg.Prefix.Name) {
857 uc.logger.Printf("changed nick from %q to %q", uc.nick, newNick)
858 me = true
859 uc.nick = newNick
860 uc.nickCM = uc.network.casemap(uc.nick)
861 }
862
863 for _, entry := range uc.channels.innerMap {
864 ch := entry.value.(*upstreamChannel)
865 memberships := ch.Members.Value(msg.Prefix.Name)
866 if memberships != nil {
867 ch.Members.Delete(msg.Prefix.Name)
868 ch.Members.SetValue(newNick, memberships)
869 uc.appendLog(ch.Name, msg)
870 }
871 }
872
873 if !me {
874 uc.forEachDownstream(func(dc *downstreamConn) {
875 dc.SendMessage(dc.marshalMessage(msg, uc.network))
876 })
877 } else {
878 uc.forEachDownstream(func(dc *downstreamConn) {
879 dc.updateNick()
880 })
881 uc.updateMonitor()
882 }
883 case "SETNAME":
884 if msg.Prefix == nil {
885 return fmt.Errorf("expected a prefix")
886 }
887
888 var newRealname string
889 if err := parseMessageParams(msg, &newRealname); err != nil {
890 return err
891 }
892
893 // TODO: consider appending this message to logs
894
895 if uc.isOurNick(msg.Prefix.Name) {
896 uc.logger.Printf("changed realname from %q to %q", uc.realname, newRealname)
897 uc.realname = newRealname
898
899 uc.forEachDownstream(func(dc *downstreamConn) {
900 dc.updateRealname()
901 })
902 } else {
903 uc.forEachDownstream(func(dc *downstreamConn) {
904 dc.SendMessage(dc.marshalMessage(msg, uc.network))
905 })
906 }
907 case "JOIN":
908 if msg.Prefix == nil {
909 return fmt.Errorf("expected a prefix")
910 }
911
912 var channels string
913 if err := parseMessageParams(msg, &channels); err != nil {
914 return err
915 }
916
917 for _, ch := range strings.Split(channels, ",") {
918 if uc.isOurNick(msg.Prefix.Name) {
919 uc.logger.Printf("joined channel %q", ch)
920 members := membershipsCasemapMap{newCasemapMap(0)}
921 members.casemap = uc.network.casemap
922 uc.channels.SetValue(ch, &upstreamChannel{
923 Name: ch,
924 conn: uc,
925 Members: members,
926 })
927 uc.updateChannelAutoDetach(ch)
928
929 uc.SendMessage(&irc.Message{
930 Command: "MODE",
931 Params: []string{ch},
932 })
933 } else {
934 ch, err := uc.getChannel(ch)
935 if err != nil {
936 return err
937 }
938 ch.Members.SetValue(msg.Prefix.Name, &memberships{})
939 }
940
941 chMsg := msg.Copy()
942 chMsg.Params[0] = ch
943 uc.produce(ch, chMsg, nil)
944 }
945 case "PART":
946 if msg.Prefix == nil {
947 return fmt.Errorf("expected a prefix")
948 }
949
950 var channels string
951 if err := parseMessageParams(msg, &channels); err != nil {
952 return err
953 }
954
955 for _, ch := range strings.Split(channels, ",") {
956 if uc.isOurNick(msg.Prefix.Name) {
957 uc.logger.Printf("parted channel %q", ch)
958 uch := uc.channels.Value(ch)
959 if uch != nil {
960 uc.channels.Delete(ch)
961 uch.updateAutoDetach(0)
962 }
963 } else {
964 ch, err := uc.getChannel(ch)
965 if err != nil {
966 return err
967 }
968 ch.Members.Delete(msg.Prefix.Name)
969 }
970
971 chMsg := msg.Copy()
972 chMsg.Params[0] = ch
973 uc.produce(ch, chMsg, nil)
974 }
975 case "KICK":
976 if msg.Prefix == nil {
977 return fmt.Errorf("expected a prefix")
978 }
979
980 var channel, user string
981 if err := parseMessageParams(msg, &channel, &user); err != nil {
982 return err
983 }
984
985 if uc.isOurNick(user) {
986 uc.logger.Printf("kicked from channel %q by %s", channel, msg.Prefix.Name)
987 uc.channels.Delete(channel)
988 } else {
989 ch, err := uc.getChannel(channel)
990 if err != nil {
991 return err
992 }
993 ch.Members.Delete(user)
994 }
995
996 uc.produce(channel, msg, nil)
997 case "QUIT":
998 if msg.Prefix == nil {
999 return fmt.Errorf("expected a prefix")
1000 }
1001
1002 if uc.isOurNick(msg.Prefix.Name) {
1003 uc.logger.Printf("quit")
1004 }
1005
1006 for _, entry := range uc.channels.innerMap {
1007 ch := entry.value.(*upstreamChannel)
1008 if ch.Members.Has(msg.Prefix.Name) {
1009 ch.Members.Delete(msg.Prefix.Name)
1010
1011 uc.appendLog(ch.Name, msg)
1012 }
1013 }
1014
1015 if msg.Prefix.Name != uc.nick {
1016 uc.forEachDownstream(func(dc *downstreamConn) {
1017 dc.SendMessage(dc.marshalMessage(msg, uc.network))
1018 })
1019 }
1020 case irc.RPL_TOPIC, irc.RPL_NOTOPIC:
1021 var name, topic string
1022 if err := parseMessageParams(msg, nil, &name, &topic); err != nil {
1023 return err
1024 }
1025 ch, err := uc.getChannel(name)
1026 if err != nil {
1027 return err
1028 }
1029 if msg.Command == irc.RPL_TOPIC {
1030 ch.Topic = topic
1031 } else {
1032 ch.Topic = ""
1033 }
1034 case "TOPIC":
1035 if msg.Prefix == nil {
1036 return fmt.Errorf("expected a prefix")
1037 }
1038
1039 var name string
1040 if err := parseMessageParams(msg, &name); err != nil {
1041 return err
1042 }
1043 ch, err := uc.getChannel(name)
1044 if err != nil {
1045 return err
1046 }
1047 if len(msg.Params) > 1 {
1048 ch.Topic = msg.Params[1]
1049 ch.TopicWho = msg.Prefix.Copy()
1050 ch.TopicTime = time.Now() // TODO use msg.Tags["time"]
1051 } else {
1052 ch.Topic = ""
1053 }
1054 uc.produce(ch.Name, msg, nil)
1055 case "MODE":
1056 var name, modeStr string
1057 if err := parseMessageParams(msg, &name, &modeStr); err != nil {
1058 return err
1059 }
1060
1061 if !uc.isChannel(name) { // user mode change
1062 if name != uc.nick {
1063 return fmt.Errorf("received MODE message for unknown nick %q", name)
1064 }
1065
1066 if err := uc.modes.Apply(modeStr); err != nil {
1067 return err
1068 }
1069
1070 uc.forEachDownstream(func(dc *downstreamConn) {
1071 if dc.upstream() == nil {
1072 return
1073 }
1074
1075 dc.SendMessage(msg)
1076 })
1077 } else { // channel mode change
1078 ch, err := uc.getChannel(name)
1079 if err != nil {
1080 return err
1081 }
1082
1083 needMarshaling, err := applyChannelModes(ch, modeStr, msg.Params[2:])
1084 if err != nil {
1085 return err
1086 }
1087
1088 uc.appendLog(ch.Name, msg)
1089
1090 c := uc.network.channels.Value(name)
1091 if c == nil || !c.Detached {
1092 uc.forEachDownstream(func(dc *downstreamConn) {
1093 params := make([]string, len(msg.Params))
1094 params[0] = dc.marshalEntity(uc.network, name)
1095 params[1] = modeStr
1096
1097 copy(params[2:], msg.Params[2:])
1098 for i, modeParam := range params[2:] {
1099 if _, ok := needMarshaling[i]; ok {
1100 params[2+i] = dc.marshalEntity(uc.network, modeParam)
1101 }
1102 }
1103
1104 dc.SendMessage(&irc.Message{
1105 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
1106 Command: "MODE",
1107 Params: params,
1108 })
1109 })
1110 }
1111 }
1112 case irc.RPL_UMODEIS:
1113 if err := parseMessageParams(msg, nil); err != nil {
1114 return err
1115 }
1116 modeStr := ""
1117 if len(msg.Params) > 1 {
1118 modeStr = msg.Params[1]
1119 }
1120
1121 uc.modes = ""
1122 if err := uc.modes.Apply(modeStr); err != nil {
1123 return err
1124 }
1125
1126 uc.forEachDownstream(func(dc *downstreamConn) {
1127 if dc.upstream() == nil {
1128 return
1129 }
1130
1131 dc.SendMessage(msg)
1132 })
1133 case irc.RPL_CHANNELMODEIS:
1134 var channel string
1135 if err := parseMessageParams(msg, nil, &channel); err != nil {
1136 return err
1137 }
1138 modeStr := ""
1139 if len(msg.Params) > 2 {
1140 modeStr = msg.Params[2]
1141 }
1142
1143 ch, err := uc.getChannel(channel)
1144 if err != nil {
1145 return err
1146 }
1147
1148 firstMode := ch.modes == nil
1149 ch.modes = make(map[byte]string)
1150 if _, err := applyChannelModes(ch, modeStr, msg.Params[3:]); err != nil {
1151 return err
1152 }
1153 if firstMode {
1154 c := uc.network.channels.Value(channel)
1155 if c == nil || !c.Detached {
1156 modeStr, modeParams := ch.modes.Format()
1157
1158 uc.forEachDownstream(func(dc *downstreamConn) {
1159 params := []string{dc.nick, dc.marshalEntity(uc.network, channel), modeStr}
1160 params = append(params, modeParams...)
1161
1162 dc.SendMessage(&irc.Message{
1163 Prefix: dc.srv.prefix(),
1164 Command: irc.RPL_CHANNELMODEIS,
1165 Params: params,
1166 })
1167 })
1168 }
1169 }
1170 case rpl_creationtime:
1171 var channel, creationTime string
1172 if err := parseMessageParams(msg, nil, &channel, &creationTime); err != nil {
1173 return err
1174 }
1175
1176 ch, err := uc.getChannel(channel)
1177 if err != nil {
1178 return err
1179 }
1180
1181 firstCreationTime := ch.creationTime == ""
1182 ch.creationTime = creationTime
1183 if firstCreationTime {
1184 uc.forEachDownstream(func(dc *downstreamConn) {
1185 dc.SendMessage(&irc.Message{
1186 Prefix: dc.srv.prefix(),
1187 Command: rpl_creationtime,
1188 Params: []string{dc.nick, dc.marshalEntity(uc.network, ch.Name), creationTime},
1189 })
1190 })
1191 }
1192 case rpl_topicwhotime:
1193 var name, who, timeStr string
1194 if err := parseMessageParams(msg, nil, &name, &who, &timeStr); err != nil {
1195 return err
1196 }
1197 ch, err := uc.getChannel(name)
1198 if err != nil {
1199 return err
1200 }
1201 firstTopicWhoTime := ch.TopicWho == nil
1202 ch.TopicWho = irc.ParsePrefix(who)
1203 sec, err := strconv.ParseInt(timeStr, 10, 64)
1204 if err != nil {
1205 return fmt.Errorf("failed to parse topic time: %v", err)
1206 }
1207 ch.TopicTime = time.Unix(sec, 0)
1208 if firstTopicWhoTime {
1209 uc.forEachDownstream(func(dc *downstreamConn) {
1210 topicWho := dc.marshalUserPrefix(uc.network, ch.TopicWho)
1211 dc.SendMessage(&irc.Message{
1212 Prefix: dc.srv.prefix(),
1213 Command: rpl_topicwhotime,
1214 Params: []string{
1215 dc.nick,
1216 dc.marshalEntity(uc.network, ch.Name),
1217 topicWho.String(),
1218 timeStr,
1219 },
1220 })
1221 })
1222 }
1223 case irc.RPL_LIST:
1224 var channel, clients, topic string
1225 if err := parseMessageParams(msg, nil, &channel, &clients, &topic); err != nil {
1226 return err
1227 }
1228
1229 dc, cmd := uc.currentPendingCommand("LIST")
1230 if cmd == nil {
1231 return fmt.Errorf("unexpected RPL_LIST: no matching pending LIST")
1232 } else if dc == nil {
1233 return nil
1234 }
1235
1236 dc.SendMessage(&irc.Message{
1237 Prefix: dc.srv.prefix(),
1238 Command: irc.RPL_LIST,
1239 Params: []string{dc.nick, dc.marshalEntity(uc.network, channel), clients, topic},
1240 })
1241 case irc.RPL_LISTEND:
1242 dc, cmd := uc.dequeueCommand("LIST")
1243 if cmd == nil {
1244 return fmt.Errorf("unexpected RPL_LISTEND: no matching pending LIST")
1245 } else if dc == nil {
1246 return nil
1247 }
1248
1249 dc.SendMessage(&irc.Message{
1250 Prefix: dc.srv.prefix(),
1251 Command: irc.RPL_LISTEND,
1252 Params: []string{dc.nick, "End of /LIST"},
1253 })
1254 case irc.RPL_NAMREPLY:
1255 var name, statusStr, members string
1256 if err := parseMessageParams(msg, nil, &statusStr, &name, &members); err != nil {
1257 return err
1258 }
1259
1260 ch := uc.channels.Value(name)
1261 if ch == nil {
1262 // NAMES on a channel we have not joined, forward to downstream
1263 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1264 channel := dc.marshalEntity(uc.network, name)
1265 members := splitSpace(members)
1266 for i, member := range members {
1267 memberships, nick := uc.parseMembershipPrefix(member)
1268 members[i] = memberships.Format(dc) + dc.marshalEntity(uc.network, nick)
1269 }
1270 memberStr := strings.Join(members, " ")
1271
1272 dc.SendMessage(&irc.Message{
1273 Prefix: dc.srv.prefix(),
1274 Command: irc.RPL_NAMREPLY,
1275 Params: []string{dc.nick, statusStr, channel, memberStr},
1276 })
1277 })
1278 return nil
1279 }
1280
1281 status, err := parseChannelStatus(statusStr)
1282 if err != nil {
1283 return err
1284 }
1285 ch.Status = status
1286
1287 for _, s := range splitSpace(members) {
1288 memberships, nick := uc.parseMembershipPrefix(s)
1289 ch.Members.SetValue(nick, memberships)
1290 }
1291 case irc.RPL_ENDOFNAMES:
1292 var name string
1293 if err := parseMessageParams(msg, nil, &name); err != nil {
1294 return err
1295 }
1296
1297 ch := uc.channels.Value(name)
1298 if ch == nil {
1299 // NAMES on a channel we have not joined, forward to downstream
1300 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1301 channel := dc.marshalEntity(uc.network, name)
1302
1303 dc.SendMessage(&irc.Message{
1304 Prefix: dc.srv.prefix(),
1305 Command: irc.RPL_ENDOFNAMES,
1306 Params: []string{dc.nick, channel, "End of /NAMES list"},
1307 })
1308 })
1309 return nil
1310 }
1311
1312 if ch.complete {
1313 return fmt.Errorf("received unexpected RPL_ENDOFNAMES")
1314 }
1315 ch.complete = true
1316
1317 c := uc.network.channels.Value(name)
1318 if c == nil || !c.Detached {
1319 uc.forEachDownstream(func(dc *downstreamConn) {
1320 forwardChannel(dc, ch)
1321 })
1322 }
1323 case irc.RPL_WHOREPLY:
1324 var channel, username, host, server, nick, mode, trailing string
1325 if err := parseMessageParams(msg, nil, &channel, &username, &host, &server, &nick, &mode, &trailing); err != nil {
1326 return err
1327 }
1328
1329 dc, cmd := uc.currentPendingCommand("WHO")
1330 if cmd == nil {
1331 return fmt.Errorf("unexpected RPL_WHOREPLY: no matching pending WHO")
1332 } else if dc == nil {
1333 return nil
1334 }
1335
1336 parts := strings.SplitN(trailing, " ", 2)
1337 if len(parts) != 2 {
1338 return fmt.Errorf("received malformed RPL_WHOREPLY: wrong trailing parameter: %s", trailing)
1339 }
1340 realname := parts[1]
1341 hops, err := strconv.Atoi(parts[0])
1342 if err != nil {
1343 return fmt.Errorf("received malformed RPL_WHOREPLY: wrong hop count: %s", parts[0])
1344 }
1345 hops++
1346
1347 trailing = strconv.Itoa(hops) + " " + realname
1348
1349 if channel != "*" {
1350 channel = dc.marshalEntity(uc.network, channel)
1351 }
1352 nick = dc.marshalEntity(uc.network, nick)
1353 dc.SendMessage(&irc.Message{
1354 Prefix: dc.srv.prefix(),
1355 Command: irc.RPL_WHOREPLY,
1356 Params: []string{dc.nick, channel, username, host, server, nick, mode, trailing},
1357 })
1358 case rpl_whospcrpl:
1359 dc, cmd := uc.currentPendingCommand("WHO")
1360 if cmd == nil {
1361 return fmt.Errorf("unexpected RPL_WHOSPCRPL: no matching pending WHO")
1362 } else if dc == nil {
1363 return nil
1364 }
1365
1366 // Only supported in single-upstream mode, so forward as-is
1367 dc.SendMessage(msg)
1368 case irc.RPL_ENDOFWHO:
1369 var name string
1370 if err := parseMessageParams(msg, nil, &name); err != nil {
1371 return err
1372 }
1373
1374 dc, cmd := uc.dequeueCommand("WHO")
1375 if cmd == nil {
1376 return fmt.Errorf("unexpected RPL_ENDOFWHO: no matching pending WHO")
1377 } else if dc == nil {
1378 return nil
1379 }
1380
1381 mask := "*"
1382 if len(cmd.Params) > 0 {
1383 mask = cmd.Params[0]
1384 }
1385 dc.SendMessage(&irc.Message{
1386 Prefix: dc.srv.prefix(),
1387 Command: irc.RPL_ENDOFWHO,
1388 Params: []string{dc.nick, mask, "End of /WHO list"},
1389 })
1390 case irc.RPL_WHOISUSER:
1391 var nick, username, host, realname string
1392 if err := parseMessageParams(msg, nil, &nick, &username, &host, nil, &realname); err != nil {
1393 return err
1394 }
1395
1396 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1397 nick := dc.marshalEntity(uc.network, nick)
1398 dc.SendMessage(&irc.Message{
1399 Prefix: dc.srv.prefix(),
1400 Command: irc.RPL_WHOISUSER,
1401 Params: []string{dc.nick, nick, username, host, "*", realname},
1402 })
1403 })
1404 case irc.RPL_WHOISSERVER:
1405 var nick, server, serverInfo string
1406 if err := parseMessageParams(msg, nil, &nick, &server, &serverInfo); err != nil {
1407 return err
1408 }
1409
1410 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1411 nick := dc.marshalEntity(uc.network, nick)
1412 dc.SendMessage(&irc.Message{
1413 Prefix: dc.srv.prefix(),
1414 Command: irc.RPL_WHOISSERVER,
1415 Params: []string{dc.nick, nick, server, serverInfo},
1416 })
1417 })
1418 case irc.RPL_WHOISOPERATOR:
1419 var nick string
1420 if err := parseMessageParams(msg, nil, &nick); err != nil {
1421 return err
1422 }
1423
1424 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1425 nick := dc.marshalEntity(uc.network, nick)
1426 dc.SendMessage(&irc.Message{
1427 Prefix: dc.srv.prefix(),
1428 Command: irc.RPL_WHOISOPERATOR,
1429 Params: []string{dc.nick, nick, "is an IRC operator"},
1430 })
1431 })
1432 case irc.RPL_WHOISIDLE:
1433 var nick string
1434 if err := parseMessageParams(msg, nil, &nick, nil); err != nil {
1435 return err
1436 }
1437
1438 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1439 nick := dc.marshalEntity(uc.network, nick)
1440 params := []string{dc.nick, nick}
1441 params = append(params, msg.Params[2:]...)
1442 dc.SendMessage(&irc.Message{
1443 Prefix: dc.srv.prefix(),
1444 Command: irc.RPL_WHOISIDLE,
1445 Params: params,
1446 })
1447 })
1448 case irc.RPL_WHOISCHANNELS:
1449 var nick, channelList string
1450 if err := parseMessageParams(msg, nil, &nick, &channelList); err != nil {
1451 return err
1452 }
1453 channels := splitSpace(channelList)
1454
1455 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1456 nick := dc.marshalEntity(uc.network, nick)
1457 channelList := make([]string, len(channels))
1458 for i, channel := range channels {
1459 prefix, channel := uc.parseMembershipPrefix(channel)
1460 channel = dc.marshalEntity(uc.network, channel)
1461 channelList[i] = prefix.Format(dc) + channel
1462 }
1463 channels := strings.Join(channelList, " ")
1464 dc.SendMessage(&irc.Message{
1465 Prefix: dc.srv.prefix(),
1466 Command: irc.RPL_WHOISCHANNELS,
1467 Params: []string{dc.nick, nick, channels},
1468 })
1469 })
1470 case irc.RPL_ENDOFWHOIS:
1471 var nick string
1472 if err := parseMessageParams(msg, nil, &nick); err != nil {
1473 return err
1474 }
1475
1476 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1477 nick := dc.marshalEntity(uc.network, nick)
1478 dc.SendMessage(&irc.Message{
1479 Prefix: dc.srv.prefix(),
1480 Command: irc.RPL_ENDOFWHOIS,
1481 Params: []string{dc.nick, nick, "End of /WHOIS list"},
1482 })
1483 })
1484 case "INVITE":
1485 var nick, channel string
1486 if err := parseMessageParams(msg, &nick, &channel); err != nil {
1487 return err
1488 }
1489
1490 weAreInvited := uc.isOurNick(nick)
1491
1492 uc.forEachDownstream(func(dc *downstreamConn) {
1493 if !weAreInvited && !dc.caps["invite-notify"] {
1494 return
1495 }
1496 dc.SendMessage(&irc.Message{
1497 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
1498 Command: "INVITE",
1499 Params: []string{dc.marshalEntity(uc.network, nick), dc.marshalEntity(uc.network, channel)},
1500 })
1501 })
1502 case irc.RPL_INVITING:
1503 var nick, channel string
1504 if err := parseMessageParams(msg, nil, &nick, &channel); err != nil {
1505 return err
1506 }
1507
1508 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1509 dc.SendMessage(&irc.Message{
1510 Prefix: dc.srv.prefix(),
1511 Command: irc.RPL_INVITING,
1512 Params: []string{dc.nick, dc.marshalEntity(uc.network, nick), dc.marshalEntity(uc.network, channel)},
1513 })
1514 })
1515 case irc.RPL_MONONLINE, irc.RPL_MONOFFLINE:
1516 var targetsStr string
1517 if err := parseMessageParams(msg, nil, &targetsStr); err != nil {
1518 return err
1519 }
1520 targets := strings.Split(targetsStr, ",")
1521
1522 online := msg.Command == irc.RPL_MONONLINE
1523 for _, target := range targets {
1524 prefix := irc.ParsePrefix(target)
1525 uc.monitored.SetValue(prefix.Name, online)
1526 }
1527
1528 // Check if the nick we want is now free
1529 wantNick := GetNick(&uc.user.User, &uc.network.Network)
1530 wantNickCM := uc.network.casemap(wantNick)
1531 if !online && uc.nickCM != wantNickCM {
1532 found := false
1533 for _, target := range targets {
1534 prefix := irc.ParsePrefix(target)
1535 if uc.network.casemap(prefix.Name) == wantNickCM {
1536 found = true
1537 break
1538 }
1539 }
1540 if found {
1541 uc.logger.Printf("desired nick %q is now available", wantNick)
1542 uc.SendMessage(&irc.Message{
1543 Command: "NICK",
1544 Params: []string{wantNick},
1545 })
1546 }
1547 }
1548
1549 uc.forEachDownstream(func(dc *downstreamConn) {
1550 for _, target := range targets {
1551 prefix := irc.ParsePrefix(target)
1552 if dc.monitored.Has(prefix.Name) {
1553 dc.SendMessage(&irc.Message{
1554 Prefix: dc.srv.prefix(),
1555 Command: msg.Command,
1556 Params: []string{dc.nick, target},
1557 })
1558 }
1559 }
1560 })
1561 case irc.ERR_MONLISTFULL:
1562 var limit, targetsStr string
1563 if err := parseMessageParams(msg, nil, &limit, &targetsStr); err != nil {
1564 return err
1565 }
1566
1567 targets := strings.Split(targetsStr, ",")
1568 uc.forEachDownstream(func(dc *downstreamConn) {
1569 for _, target := range targets {
1570 if dc.monitored.Has(target) {
1571 dc.SendMessage(&irc.Message{
1572 Prefix: dc.srv.prefix(),
1573 Command: msg.Command,
1574 Params: []string{dc.nick, limit, target},
1575 })
1576 }
1577 }
1578 })
1579 case irc.RPL_AWAY:
1580 var nick, reason string
1581 if err := parseMessageParams(msg, nil, &nick, &reason); err != nil {
1582 return err
1583 }
1584
1585 uc.forEachDownstream(func(dc *downstreamConn) {
1586 dc.SendMessage(&irc.Message{
1587 Prefix: dc.srv.prefix(),
1588 Command: irc.RPL_AWAY,
1589 Params: []string{dc.nick, dc.marshalEntity(uc.network, nick), reason},
1590 })
1591 })
1592 case "AWAY", "ACCOUNT":
1593 if msg.Prefix == nil {
1594 return fmt.Errorf("expected a prefix")
1595 }
1596
1597 uc.forEachDownstream(func(dc *downstreamConn) {
1598 dc.SendMessage(&irc.Message{
1599 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
1600 Command: msg.Command,
1601 Params: msg.Params,
1602 })
1603 })
1604 case irc.RPL_BANLIST, irc.RPL_INVITELIST, irc.RPL_EXCEPTLIST:
1605 var channel, mask string
1606 if err := parseMessageParams(msg, nil, &channel, &mask); err != nil {
1607 return err
1608 }
1609 var addNick, addTime string
1610 if len(msg.Params) >= 5 {
1611 addNick = msg.Params[3]
1612 addTime = msg.Params[4]
1613 }
1614
1615 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1616 channel := dc.marshalEntity(uc.network, channel)
1617
1618 var params []string
1619 if addNick != "" && addTime != "" {
1620 addNick := dc.marshalEntity(uc.network, addNick)
1621 params = []string{dc.nick, channel, mask, addNick, addTime}
1622 } else {
1623 params = []string{dc.nick, channel, mask}
1624 }
1625
1626 dc.SendMessage(&irc.Message{
1627 Prefix: dc.srv.prefix(),
1628 Command: msg.Command,
1629 Params: params,
1630 })
1631 })
1632 case irc.RPL_ENDOFBANLIST, irc.RPL_ENDOFINVITELIST, irc.RPL_ENDOFEXCEPTLIST:
1633 var channel, trailing string
1634 if err := parseMessageParams(msg, nil, &channel, &trailing); err != nil {
1635 return err
1636 }
1637
1638 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1639 upstreamChannel := dc.marshalEntity(uc.network, channel)
1640 dc.SendMessage(&irc.Message{
1641 Prefix: dc.srv.prefix(),
1642 Command: msg.Command,
1643 Params: []string{dc.nick, upstreamChannel, trailing},
1644 })
1645 })
1646 case irc.ERR_UNKNOWNCOMMAND, irc.RPL_TRYAGAIN:
1647 var command, reason string
1648 if err := parseMessageParams(msg, nil, &command, &reason); err != nil {
1649 return err
1650 }
1651
1652 if dc, _ := uc.dequeueCommand(command); dc != nil && downstreamID == 0 {
1653 downstreamID = dc.id
1654 }
1655
1656 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1657 dc.SendMessage(&irc.Message{
1658 Prefix: uc.srv.prefix(),
1659 Command: msg.Command,
1660 Params: []string{dc.nick, command, reason},
1661 })
1662 })
1663 case "FAIL":
1664 var command, code string
1665 if err := parseMessageParams(msg, &command, &code); err != nil {
1666 return err
1667 }
1668
1669 if !uc.registered && command == "*" && code == "ACCOUNT_REQUIRED" {
1670 return registrationError{msg}
1671 }
1672
1673 if dc, _ := uc.dequeueCommand(command); dc != nil && downstreamID == 0 {
1674 downstreamID = dc.id
1675 }
1676
1677 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1678 dc.SendMessage(msg)
1679 })
1680 case "ACK":
1681 // Ignore
1682 case irc.RPL_NOWAWAY, irc.RPL_UNAWAY:
1683 // Ignore
1684 case irc.RPL_YOURHOST, irc.RPL_CREATED:
1685 // Ignore
1686 case irc.RPL_LUSERCLIENT, irc.RPL_LUSEROP, irc.RPL_LUSERUNKNOWN, irc.RPL_LUSERCHANNELS, irc.RPL_LUSERME:
1687 fallthrough
1688 case irc.RPL_STATSVLINE, rpl_statsping, irc.RPL_STATSBLINE, irc.RPL_STATSDLINE:
1689 fallthrough
1690 case rpl_localusers, rpl_globalusers:
1691 fallthrough
1692 case irc.RPL_MOTDSTART, irc.RPL_MOTD:
1693 // Ignore these messages if they're part of the initial registration
1694 // message burst. Forward them if the user explicitly asked for them.
1695 if !uc.gotMotd {
1696 return nil
1697 }
1698
1699 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1700 dc.SendMessage(&irc.Message{
1701 Prefix: uc.srv.prefix(),
1702 Command: msg.Command,
1703 Params: msg.Params,
1704 })
1705 })
1706 case irc.RPL_LISTSTART:
1707 // Ignore
1708 case "ERROR":
1709 var text string
1710 if err := parseMessageParams(msg, &text); err != nil {
1711 return err
1712 }
1713 return fmt.Errorf("fatal server error: %v", text)
1714 case irc.ERR_NICKNAMEINUSE:
1715 // At this point, we haven't received ISUPPORT so we don't know the
1716 // maximum nickname length or whether the server supports MONITOR. Many
1717 // servers have NICKLEN=30 so let's just use that.
1718 if !uc.registered && len(uc.nick)+1 < 30 {
1719 uc.nick = uc.nick + "_"
1720 uc.nickCM = uc.network.casemap(uc.nick)
1721 uc.logger.Printf("desired nick is not available, falling back to %q", uc.nick)
1722 uc.SendMessage(&irc.Message{
1723 Command: "NICK",
1724 Params: []string{uc.nick},
1725 })
1726 return nil
1727 }
1728 fallthrough
1729 case irc.ERR_PASSWDMISMATCH, irc.ERR_ERRONEUSNICKNAME, irc.ERR_NICKCOLLISION, irc.ERR_UNAVAILRESOURCE, irc.ERR_NOPERMFORHOST, irc.ERR_YOUREBANNEDCREEP:
1730 if !uc.registered {
1731 return registrationError{msg}
1732 }
1733 fallthrough
1734 default:
1735 uc.logger.Printf("unhandled message: %v", msg)
1736
1737 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1738 // best effort marshaling for unknown messages, replies and errors:
1739 // most numerics start with the user nick, marshal it if that's the case
1740 // otherwise, conservately keep the params without marshaling
1741 params := msg.Params
1742 if _, err := strconv.Atoi(msg.Command); err == nil { // numeric
1743 if len(msg.Params) > 0 && isOurNick(uc.network, msg.Params[0]) {
1744 params[0] = dc.nick
1745 }
1746 }
1747 dc.SendMessage(&irc.Message{
1748 Prefix: uc.srv.prefix(),
1749 Command: msg.Command,
1750 Params: params,
1751 })
1752 })
1753 }
1754 return nil
1755}
1756
1757func (uc *upstreamConn) handleDetachedMessage(ctx context.Context, ch *Channel, msg *irc.Message) {
1758 if uc.network.detachedMessageNeedsRelay(ch, msg) {
1759 uc.forEachDownstream(func(dc *downstreamConn) {
1760 dc.relayDetachedMessage(uc.network, msg)
1761 })
1762 }
1763 if ch.ReattachOn == FilterMessage || (ch.ReattachOn == FilterHighlight && uc.network.isHighlight(msg)) {
1764 uc.network.attach(ch)
1765 if err := uc.srv.db.StoreChannel(ctx, uc.network.ID, ch); err != nil {
1766 uc.logger.Printf("failed to update channel %q: %v", ch.Name, err)
1767 }
1768 }
1769}
1770
1771func (uc *upstreamConn) handleChanModes(s string) error {
1772 parts := strings.SplitN(s, ",", 5)
1773 if len(parts) < 4 {
1774 return fmt.Errorf("malformed ISUPPORT CHANMODES value: %v", s)
1775 }
1776 modes := make(map[byte]channelModeType)
1777 for i, mt := range []channelModeType{modeTypeA, modeTypeB, modeTypeC, modeTypeD} {
1778 for j := 0; j < len(parts[i]); j++ {
1779 mode := parts[i][j]
1780 modes[mode] = mt
1781 }
1782 }
1783 uc.availableChannelModes = modes
1784 return nil
1785}
1786
1787func (uc *upstreamConn) handleMemberships(s string) error {
1788 if s == "" {
1789 uc.availableMemberships = nil
1790 return nil
1791 }
1792
1793 if s[0] != '(' {
1794 return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", s)
1795 }
1796 sep := strings.IndexByte(s, ')')
1797 if sep < 0 || len(s) != sep*2 {
1798 return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", s)
1799 }
1800 memberships := make([]membership, len(s)/2-1)
1801 for i := range memberships {
1802 memberships[i] = membership{
1803 Mode: s[i+1],
1804 Prefix: s[sep+i+1],
1805 }
1806 }
1807 uc.availableMemberships = memberships
1808 return nil
1809}
1810
1811func (uc *upstreamConn) handleSupportedCaps(capsStr string) {
1812 caps := strings.Fields(capsStr)
1813 for _, s := range caps {
1814 kv := strings.SplitN(s, "=", 2)
1815 k := strings.ToLower(kv[0])
1816 var v string
1817 if len(kv) == 2 {
1818 v = kv[1]
1819 }
1820 uc.supportedCaps[k] = v
1821 }
1822}
1823
1824func (uc *upstreamConn) requestCaps() {
1825 var requestCaps []string
1826 for c := range permanentUpstreamCaps {
1827 if _, ok := uc.supportedCaps[c]; ok && !uc.caps[c] {
1828 requestCaps = append(requestCaps, c)
1829 }
1830 }
1831
1832 if len(requestCaps) == 0 {
1833 return
1834 }
1835
1836 uc.SendMessage(&irc.Message{
1837 Command: "CAP",
1838 Params: []string{"REQ", strings.Join(requestCaps, " ")},
1839 })
1840}
1841
1842func (uc *upstreamConn) supportsSASL(mech string) bool {
1843 v, ok := uc.supportedCaps["sasl"]
1844 if !ok {
1845 return false
1846 }
1847
1848 if v == "" {
1849 return true
1850 }
1851
1852 mechanisms := strings.Split(v, ",")
1853 for _, mech := range mechanisms {
1854 if strings.EqualFold(mech, mech) {
1855 return true
1856 }
1857 }
1858 return false
1859}
1860
1861func (uc *upstreamConn) requestSASL() bool {
1862 if uc.network.SASL.Mechanism == "" {
1863 return false
1864 }
1865 return uc.supportsSASL(uc.network.SASL.Mechanism)
1866}
1867
1868func (uc *upstreamConn) handleCapAck(name string, ok bool) error {
1869 uc.caps[name] = ok
1870
1871 switch name {
1872 case "sasl":
1873 if !uc.requestSASL() {
1874 return nil
1875 }
1876 if !ok {
1877 uc.logger.Printf("server refused to acknowledge the SASL capability")
1878 return nil
1879 }
1880
1881 auth := &uc.network.SASL
1882 switch auth.Mechanism {
1883 case "PLAIN":
1884 uc.logger.Printf("starting SASL PLAIN authentication with username %q", auth.Plain.Username)
1885 uc.saslClient = sasl.NewPlainClient("", auth.Plain.Username, auth.Plain.Password)
1886 case "EXTERNAL":
1887 uc.logger.Printf("starting SASL EXTERNAL authentication")
1888 uc.saslClient = sasl.NewExternalClient("")
1889 default:
1890 return fmt.Errorf("unsupported SASL mechanism %q", name)
1891 }
1892
1893 uc.SendMessage(&irc.Message{
1894 Command: "AUTHENTICATE",
1895 Params: []string{auth.Mechanism},
1896 })
1897 default:
1898 if permanentUpstreamCaps[name] {
1899 break
1900 }
1901 uc.logger.Printf("received CAP ACK/NAK for a cap we don't support: %v", name)
1902 }
1903 return nil
1904}
1905
1906func splitSpace(s string) []string {
1907 return strings.FieldsFunc(s, func(r rune) bool {
1908 return r == ' '
1909 })
1910}
1911
1912func (uc *upstreamConn) register() {
1913 uc.nick = GetNick(&uc.user.User, &uc.network.Network)
1914 uc.nickCM = uc.network.casemap(uc.nick)
1915 uc.username = GetUsername(&uc.user.User, &uc.network.Network)
1916 uc.realname = GetRealname(&uc.user.User, &uc.network.Network)
1917
1918 uc.SendMessage(&irc.Message{
1919 Command: "CAP",
1920 Params: []string{"LS", "302"},
1921 })
1922
1923 if uc.network.Pass != "" {
1924 uc.SendMessage(&irc.Message{
1925 Command: "PASS",
1926 Params: []string{uc.network.Pass},
1927 })
1928 }
1929
1930 uc.SendMessage(&irc.Message{
1931 Command: "NICK",
1932 Params: []string{uc.nick},
1933 })
1934 uc.SendMessage(&irc.Message{
1935 Command: "USER",
1936 Params: []string{uc.username, "0", "*", uc.realname},
1937 })
1938}
1939
1940func (uc *upstreamConn) ReadMessage() (*irc.Message, error) {
1941 msg, err := uc.conn.ReadMessage()
1942 if err != nil {
1943 return nil, err
1944 }
1945 uc.srv.metrics.upstreamInMessagesTotal.Inc()
1946 return msg, nil
1947}
1948
1949func (uc *upstreamConn) runUntilRegistered() error {
1950 for !uc.registered {
1951 msg, err := uc.ReadMessage()
1952 if err != nil {
1953 return fmt.Errorf("failed to read message: %v", err)
1954 }
1955
1956 if err := uc.handleMessage(context.TODO(), msg); err != nil {
1957 if _, ok := err.(registrationError); ok {
1958 return err
1959 } else {
1960 msg.Tags = nil // prevent message tags from cluttering logs
1961 return fmt.Errorf("failed to handle message %q: %v", msg, err)
1962 }
1963 }
1964 }
1965
1966 for _, command := range uc.network.ConnectCommands {
1967 m, err := irc.ParseMessage(command)
1968 if err != nil {
1969 uc.logger.Printf("failed to parse connect command %q: %v", command, err)
1970 } else {
1971 uc.SendMessage(m)
1972 }
1973 }
1974
1975 return nil
1976}
1977
1978func (uc *upstreamConn) readMessages(ch chan<- event) error {
1979 for {
1980 msg, err := uc.ReadMessage()
1981 if errors.Is(err, io.EOF) {
1982 break
1983 } else if err != nil {
1984 return fmt.Errorf("failed to read IRC command: %v", err)
1985 }
1986
1987 ch <- eventUpstreamMessage{msg, uc}
1988 }
1989
1990 return nil
1991}
1992
1993func (uc *upstreamConn) SendMessage(msg *irc.Message) {
1994 if !uc.caps["message-tags"] {
1995 msg = msg.Copy()
1996 msg.Tags = nil
1997 }
1998
1999 uc.srv.metrics.upstreamOutMessagesTotal.Inc()
2000 uc.conn.SendMessage(msg)
2001}
2002
2003func (uc *upstreamConn) SendMessageLabeled(downstreamID uint64, msg *irc.Message) {
2004 if uc.caps["labeled-response"] {
2005 if msg.Tags == nil {
2006 msg.Tags = make(map[string]irc.TagValue)
2007 }
2008 msg.Tags["label"] = irc.TagValue(fmt.Sprintf("sd-%d-%d", downstreamID, uc.nextLabelID))
2009 uc.nextLabelID++
2010 }
2011 uc.SendMessage(msg)
2012}
2013
2014// appendLog appends a message to the log file.
2015//
2016// The internal message ID is returned. If the message isn't recorded in the
2017// log file, an empty string is returned.
2018func (uc *upstreamConn) appendLog(entity string, msg *irc.Message) (msgID string) {
2019 if uc.user.msgStore == nil {
2020 return ""
2021 }
2022
2023 // Don't store messages with a server mask target
2024 if strings.HasPrefix(entity, "$") {
2025 return ""
2026 }
2027
2028 entityCM := uc.network.casemap(entity)
2029 if entityCM == "nickserv" {
2030 // The messages sent/received from NickServ may contain
2031 // security-related information (like passwords). Don't store these.
2032 return ""
2033 }
2034
2035 if !uc.network.delivered.HasTarget(entity) {
2036 // This is the first message we receive from this target. Save the last
2037 // message ID in delivery receipts, so that we can send the new message
2038 // in the backlog if an offline client reconnects.
2039 lastID, err := uc.user.msgStore.LastMsgID(&uc.network.Network, entityCM, time.Now())
2040 if err != nil {
2041 uc.logger.Printf("failed to log message: failed to get last message ID: %v", err)
2042 return ""
2043 }
2044
2045 uc.network.delivered.ForEachClient(func(clientName string) {
2046 uc.network.delivered.StoreID(entity, clientName, lastID)
2047 })
2048 }
2049
2050 msgID, err := uc.user.msgStore.Append(&uc.network.Network, entityCM, msg)
2051 if err != nil {
2052 uc.logger.Printf("failed to log message: %v", err)
2053 return ""
2054 }
2055
2056 return msgID
2057}
2058
2059// produce appends a message to the logs and forwards it to connected downstream
2060// connections.
2061//
2062// If origin is not nil and origin doesn't support echo-message, the message is
2063// forwarded to all connections except origin.
2064func (uc *upstreamConn) produce(target string, msg *irc.Message, origin *downstreamConn) {
2065 var msgID string
2066 if target != "" {
2067 msgID = uc.appendLog(target, msg)
2068 }
2069
2070 // Don't forward messages if it's a detached channel
2071 ch := uc.network.channels.Value(target)
2072 detached := ch != nil && ch.Detached
2073
2074 uc.forEachDownstream(func(dc *downstreamConn) {
2075 if !detached && (dc != origin || dc.caps["echo-message"]) {
2076 dc.sendMessageWithID(dc.marshalMessage(msg, uc.network), msgID)
2077 } else {
2078 dc.advanceMessageWithID(msg, msgID)
2079 }
2080 })
2081}
2082
2083func (uc *upstreamConn) updateAway() {
2084 away := true
2085 uc.forEachDownstream(func(*downstreamConn) {
2086 away = false
2087 })
2088 if away == uc.away {
2089 return
2090 }
2091 if away {
2092 uc.SendMessage(&irc.Message{
2093 Command: "AWAY",
2094 Params: []string{"Auto away"},
2095 })
2096 } else {
2097 uc.SendMessage(&irc.Message{
2098 Command: "AWAY",
2099 })
2100 }
2101 uc.away = away
2102}
2103
2104func (uc *upstreamConn) updateChannelAutoDetach(name string) {
2105 uch := uc.channels.Value(name)
2106 if uch == nil {
2107 return
2108 }
2109 ch := uc.network.channels.Value(name)
2110 if ch == nil || ch.Detached {
2111 return
2112 }
2113 uch.updateAutoDetach(ch.DetachAfter)
2114}
2115
2116func (uc *upstreamConn) updateMonitor() {
2117 if _, ok := uc.isupport["MONITOR"]; !ok {
2118 return
2119 }
2120
2121 add := make(map[string]struct{})
2122 var addList []string
2123 seen := make(map[string]struct{})
2124 uc.forEachDownstream(func(dc *downstreamConn) {
2125 for targetCM := range dc.monitored.innerMap {
2126 if !uc.monitored.Has(targetCM) {
2127 if _, ok := add[targetCM]; !ok {
2128 addList = append(addList, targetCM)
2129 add[targetCM] = struct{}{}
2130 }
2131 } else {
2132 seen[targetCM] = struct{}{}
2133 }
2134 }
2135 })
2136
2137 wantNick := GetNick(&uc.user.User, &uc.network.Network)
2138 wantNickCM := uc.network.casemap(wantNick)
2139 if _, ok := add[wantNickCM]; !ok && !uc.monitored.Has(wantNick) && !uc.isOurNick(wantNick) {
2140 addList = append(addList, wantNickCM)
2141 add[wantNickCM] = struct{}{}
2142 }
2143
2144 removeAll := true
2145 var removeList []string
2146 for targetCM, entry := range uc.monitored.innerMap {
2147 if _, ok := seen[targetCM]; ok {
2148 removeAll = false
2149 } else {
2150 removeList = append(removeList, entry.originalKey)
2151 }
2152 }
2153
2154 // TODO: better handle the case where len(uc.monitored) + len(addList)
2155 // exceeds the limit, probably by immediately sending ERR_MONLISTFULL?
2156
2157 if removeAll && len(addList) == 0 && len(removeList) > 0 {
2158 // Optimization when the last MONITOR-aware downstream disconnects
2159 uc.SendMessage(&irc.Message{
2160 Command: "MONITOR",
2161 Params: []string{"C"},
2162 })
2163 } else {
2164 msgs := generateMonitor("-", removeList)
2165 msgs = append(msgs, generateMonitor("+", addList)...)
2166 for _, msg := range msgs {
2167 uc.SendMessage(msg)
2168 }
2169 }
2170
2171 for _, target := range removeList {
2172 uc.monitored.Delete(target)
2173 }
2174}
Note: See TracBrowser for help on using the repository browser.