source: code/trunk/upstream.go@ 293

Last change on this file since 293 was 293, checked in by delthas, 5 years ago

Fix parsing MODE messages by updating channel memberships

Previously, we only considered channel modes in the modes of a MODE
messages, which means channel membership changes were ignored. This
resulted in bugs where users channel memberships would not be properly
updated and cached with wrong values. Further, mode arguments
representing entities were not properly marshaled.

This adds support for correctly parsing and updating channel memberships
when processing MODE messages. Mode arguments corresponding to channel
memberships updates are now also properly marshaled.

MODE messages can't be easily sent from history because marshaling these
messages require knowing about the upstream available channel types and
channel membership types, which is currently only possible when
connected. For now this is not an issue since we do not send MODE
messages in history.

File size: 37.7 KB
Line 
1package soju
2
3import (
4 "crypto/tls"
5 "encoding/base64"
6 "errors"
7 "fmt"
8 "io"
9 "net"
10 "strconv"
11 "strings"
12 "time"
13 "unicode"
14 "unicode/utf8"
15
16 "github.com/emersion/go-sasl"
17 "gopkg.in/irc.v3"
18)
19
20// permanentUpstreamCaps is the static list of upstream capabilities always
21// requested when supported.
22var permanentUpstreamCaps = map[string]bool{
23 "away-notify": true,
24 "batch": true,
25 "labeled-response": true,
26 "message-tags": true,
27 "multi-prefix": true,
28 "server-time": true,
29}
30
31type upstreamChannel struct {
32 Name string
33 conn *upstreamConn
34 Topic string
35 TopicWho string
36 TopicTime time.Time
37 Status channelStatus
38 modes channelModes
39 creationTime string
40 Members map[string]*memberships
41 complete bool
42}
43
44type upstreamConn struct {
45 conn
46
47 network *network
48 user *user
49
50 serverName string
51 availableUserModes string
52 availableChannelModes map[byte]channelModeType
53 availableChannelTypes string
54 availableMemberships []membership
55
56 registered bool
57 nick string
58 username string
59 realname string
60 modes userModes
61 channels map[string]*upstreamChannel
62 supportedCaps map[string]string
63 caps map[string]bool
64 batches map[string]batch
65 away bool
66 nextLabelID uint64
67
68 saslClient sasl.Client
69 saslStarted bool
70
71 // set of LIST commands in progress, per downstream
72 pendingLISTDownstreamSet map[uint64]struct{}
73
74 messageLoggers map[string]*messageLogger
75}
76
77func connectToUpstream(network *network) (*upstreamConn, error) {
78 logger := &prefixLogger{network.user.srv.Logger, fmt.Sprintf("upstream %q: ", network.Addr)}
79
80 var scheme string
81 var addr string
82
83 addrParts := strings.SplitN(network.Addr, "://", 2)
84 if len(addrParts) == 2 {
85 scheme = addrParts[0]
86 addr = addrParts[1]
87 } else {
88 scheme = "ircs"
89 addr = addrParts[0]
90 }
91
92 dialer := net.Dialer{Timeout: connectTimeout}
93
94 var netConn net.Conn
95 var err error
96 switch scheme {
97 case "ircs":
98 if !strings.ContainsRune(addr, ':') {
99 addr = addr + ":6697"
100 }
101
102 logger.Printf("connecting to TLS server at address %q", addr)
103 netConn, err = tls.DialWithDialer(&dialer, "tcp", addr, nil)
104 case "irc+insecure":
105 if !strings.ContainsRune(addr, ':') {
106 addr = addr + ":6667"
107 }
108
109 logger.Printf("connecting to plain-text server at address %q", addr)
110 netConn, err = dialer.Dial("tcp", addr)
111 default:
112 return nil, fmt.Errorf("failed to dial %q: unknown scheme: %v", addr, scheme)
113 }
114 if err != nil {
115 return nil, fmt.Errorf("failed to dial %q: %v", addr, err)
116 }
117
118 uc := &upstreamConn{
119 conn: *newConn(network.user.srv, netConn, logger),
120 network: network,
121 user: network.user,
122 channels: make(map[string]*upstreamChannel),
123 supportedCaps: make(map[string]string),
124 caps: make(map[string]bool),
125 batches: make(map[string]batch),
126 availableChannelTypes: stdChannelTypes,
127 availableChannelModes: stdChannelModes,
128 availableMemberships: stdMemberships,
129 pendingLISTDownstreamSet: make(map[uint64]struct{}),
130 messageLoggers: make(map[string]*messageLogger),
131 }
132
133 return uc, nil
134}
135
136func (uc *upstreamConn) forEachDownstream(f func(*downstreamConn)) {
137 uc.network.forEachDownstream(f)
138}
139
140func (uc *upstreamConn) forEachDownstreamByID(id uint64, f func(*downstreamConn)) {
141 uc.forEachDownstream(func(dc *downstreamConn) {
142 if id != 0 && id != dc.id {
143 return
144 }
145 f(dc)
146 })
147}
148
149func (uc *upstreamConn) getChannel(name string) (*upstreamChannel, error) {
150 ch, ok := uc.channels[name]
151 if !ok {
152 return nil, fmt.Errorf("unknown channel %q", name)
153 }
154 return ch, nil
155}
156
157func (uc *upstreamConn) isChannel(entity string) bool {
158 if i := strings.IndexByte(uc.availableChannelTypes, entity[0]); i >= 0 {
159 return true
160 }
161 return false
162}
163
164func (uc *upstreamConn) getPendingLIST() *pendingLIST {
165 for _, pl := range uc.user.pendingLISTs {
166 if _, ok := pl.pendingCommands[uc.network.ID]; !ok {
167 continue
168 }
169 return &pl
170 }
171 return nil
172}
173
174func (uc *upstreamConn) endPendingLISTs(all bool) (found bool) {
175 found = false
176 for i := 0; i < len(uc.user.pendingLISTs); i++ {
177 pl := uc.user.pendingLISTs[i]
178 if _, ok := pl.pendingCommands[uc.network.ID]; !ok {
179 continue
180 }
181 delete(pl.pendingCommands, uc.network.ID)
182 if len(pl.pendingCommands) == 0 {
183 uc.user.pendingLISTs = append(uc.user.pendingLISTs[:i], uc.user.pendingLISTs[i+1:]...)
184 i--
185 uc.forEachDownstreamByID(pl.downstreamID, func(dc *downstreamConn) {
186 dc.SendMessage(&irc.Message{
187 Prefix: dc.srv.prefix(),
188 Command: irc.RPL_LISTEND,
189 Params: []string{dc.nick, "End of /LIST"},
190 })
191 })
192 }
193 found = true
194 if !all {
195 delete(uc.pendingLISTDownstreamSet, pl.downstreamID)
196 uc.user.forEachUpstream(func(uc *upstreamConn) {
197 uc.trySendLIST(pl.downstreamID)
198 })
199 return
200 }
201 }
202 return
203}
204
205func (uc *upstreamConn) trySendLIST(downstreamID uint64) {
206 if _, ok := uc.pendingLISTDownstreamSet[downstreamID]; ok {
207 // a LIST command is already pending
208 // we will try again when that command is completed
209 return
210 }
211
212 for _, pl := range uc.user.pendingLISTs {
213 if pl.downstreamID != downstreamID {
214 continue
215 }
216 // this is the first pending LIST command list of the downstream
217 listCommand, ok := pl.pendingCommands[uc.network.ID]
218 if !ok {
219 // there is no command for this upstream in these LIST commands
220 // do not send anything
221 continue
222 }
223 // there is a command for this upstream in these LIST commands
224 // send it now
225
226 uc.SendMessageLabeled(downstreamID, listCommand)
227
228 uc.pendingLISTDownstreamSet[downstreamID] = struct{}{}
229 return
230 }
231}
232
233func (uc *upstreamConn) parseMembershipPrefix(s string) (ms *memberships, nick string) {
234 memberships := make(memberships, 0, 4)
235 i := 0
236 for _, m := range uc.availableMemberships {
237 if i >= len(s) {
238 break
239 }
240 if s[i] == m.Prefix {
241 memberships = append(memberships, m)
242 i++
243 }
244 }
245 return &memberships, s[i:]
246}
247
248func isWordBoundary(r rune) bool {
249 switch r {
250 case '-', '_', '|':
251 return false
252 case '\u00A0':
253 return true
254 default:
255 return !unicode.IsLetter(r) && !unicode.IsNumber(r)
256 }
257}
258
259func isHighlight(text, nick string) bool {
260 for {
261 i := strings.Index(text, nick)
262 if i < 0 {
263 return false
264 }
265
266 // Detect word boundaries
267 var left, right rune
268 if i > 0 {
269 left, _ = utf8.DecodeLastRuneInString(text[:i])
270 }
271 if i < len(text) {
272 right, _ = utf8.DecodeRuneInString(text[i+len(nick):])
273 }
274 if isWordBoundary(left) && isWordBoundary(right) {
275 return true
276 }
277
278 text = text[i+len(nick):]
279 }
280}
281
282func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
283 var label string
284 if l, ok := msg.GetTag("label"); ok {
285 label = l
286 }
287
288 var msgBatch *batch
289 if batchName, ok := msg.GetTag("batch"); ok {
290 b, ok := uc.batches[batchName]
291 if !ok {
292 return fmt.Errorf("unexpected batch reference: batch was not defined: %q", batchName)
293 }
294 msgBatch = &b
295 if label == "" {
296 label = msgBatch.Label
297 }
298 }
299
300 var downstreamID uint64 = 0
301 if label != "" {
302 var labelOffset uint64
303 n, err := fmt.Sscanf(label, "sd-%d-%d", &downstreamID, &labelOffset)
304 if err == nil && n < 2 {
305 err = errors.New("not enough arguments")
306 }
307 if err != nil {
308 return fmt.Errorf("unexpected message label: invalid downstream reference for label %q: %v", label, err)
309 }
310 }
311
312 if _, ok := msg.Tags["time"]; !ok {
313 msg.Tags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
314 }
315
316 switch msg.Command {
317 case "PING":
318 uc.SendMessage(&irc.Message{
319 Command: "PONG",
320 Params: msg.Params,
321 })
322 return nil
323 case "NOTICE", "PRIVMSG":
324 if msg.Prefix == nil {
325 return fmt.Errorf("expected a prefix")
326 }
327
328 var entity, text string
329 if err := parseMessageParams(msg, &entity, &text); err != nil {
330 return err
331 }
332
333 if msg.Prefix.Name == serviceNick {
334 uc.logger.Printf("skipping %v from soju's service: %v", msg.Command, msg)
335 break
336 }
337 if entity == serviceNick {
338 uc.logger.Printf("skipping %v to soju's service: %v", msg.Command, msg)
339 break
340 }
341
342 if msg.Prefix.User == "" && msg.Prefix.Host == "" { // server message
343 uc.produce("", msg, nil)
344 } else { // regular user NOTICE or PRIVMSG
345 target := entity
346 if target == uc.nick {
347 target = msg.Prefix.Name
348 }
349 uc.produce(target, msg, nil)
350
351 highlight := msg.Prefix.Name != uc.nick && isHighlight(text, uc.nick)
352 if ch, ok := uc.network.channels[target]; ok && ch.Detached && highlight {
353 uc.forEachDownstream(func(dc *downstreamConn) {
354 sendServiceNOTICE(dc, fmt.Sprintf("highlight in %v: <%v> %v", dc.marshalEntity(uc.network, ch.Name), msg.Prefix.Name, text))
355 })
356 }
357 }
358 case "CAP":
359 var subCmd string
360 if err := parseMessageParams(msg, nil, &subCmd); err != nil {
361 return err
362 }
363 subCmd = strings.ToUpper(subCmd)
364 subParams := msg.Params[2:]
365 switch subCmd {
366 case "LS":
367 if len(subParams) < 1 {
368 return newNeedMoreParamsError(msg.Command)
369 }
370 caps := subParams[len(subParams)-1]
371 more := len(subParams) >= 2 && msg.Params[len(subParams)-2] == "*"
372
373 uc.handleSupportedCaps(caps)
374
375 if more {
376 break // wait to receive all capabilities
377 }
378
379 uc.requestCaps()
380
381 if uc.requestSASL() {
382 break // we'll send CAP END after authentication is completed
383 }
384
385 uc.SendMessage(&irc.Message{
386 Command: "CAP",
387 Params: []string{"END"},
388 })
389 case "ACK", "NAK":
390 if len(subParams) < 1 {
391 return newNeedMoreParamsError(msg.Command)
392 }
393 caps := strings.Fields(subParams[0])
394
395 for _, name := range caps {
396 if err := uc.handleCapAck(strings.ToLower(name), subCmd == "ACK"); err != nil {
397 return err
398 }
399 }
400
401 if uc.saslClient == nil {
402 uc.SendMessage(&irc.Message{
403 Command: "CAP",
404 Params: []string{"END"},
405 })
406 }
407
408 if uc.registered {
409 uc.forEachDownstream(func(dc *downstreamConn) {
410 dc.updateSupportedCaps()
411 })
412 }
413 case "NEW":
414 if len(subParams) < 1 {
415 return newNeedMoreParamsError(msg.Command)
416 }
417 uc.handleSupportedCaps(subParams[0])
418 uc.requestCaps()
419 case "DEL":
420 if len(subParams) < 1 {
421 return newNeedMoreParamsError(msg.Command)
422 }
423 caps := strings.Fields(subParams[0])
424
425 for _, c := range caps {
426 delete(uc.supportedCaps, c)
427 delete(uc.caps, c)
428 }
429
430 if uc.registered {
431 uc.forEachDownstream(func(dc *downstreamConn) {
432 dc.updateSupportedCaps()
433 })
434 }
435 default:
436 uc.logger.Printf("unhandled message: %v", msg)
437 }
438 case "AUTHENTICATE":
439 if uc.saslClient == nil {
440 return fmt.Errorf("received unexpected AUTHENTICATE message")
441 }
442
443 // TODO: if a challenge is 400 bytes long, buffer it
444 var challengeStr string
445 if err := parseMessageParams(msg, &challengeStr); err != nil {
446 uc.SendMessage(&irc.Message{
447 Command: "AUTHENTICATE",
448 Params: []string{"*"},
449 })
450 return err
451 }
452
453 var challenge []byte
454 if challengeStr != "+" {
455 var err error
456 challenge, err = base64.StdEncoding.DecodeString(challengeStr)
457 if err != nil {
458 uc.SendMessage(&irc.Message{
459 Command: "AUTHENTICATE",
460 Params: []string{"*"},
461 })
462 return err
463 }
464 }
465
466 var resp []byte
467 var err error
468 if !uc.saslStarted {
469 _, resp, err = uc.saslClient.Start()
470 uc.saslStarted = true
471 } else {
472 resp, err = uc.saslClient.Next(challenge)
473 }
474 if err != nil {
475 uc.SendMessage(&irc.Message{
476 Command: "AUTHENTICATE",
477 Params: []string{"*"},
478 })
479 return err
480 }
481
482 // TODO: send response in multiple chunks if >= 400 bytes
483 var respStr = "+"
484 if resp != nil {
485 respStr = base64.StdEncoding.EncodeToString(resp)
486 }
487
488 uc.SendMessage(&irc.Message{
489 Command: "AUTHENTICATE",
490 Params: []string{respStr},
491 })
492 case irc.RPL_LOGGEDIN:
493 var account string
494 if err := parseMessageParams(msg, nil, nil, &account); err != nil {
495 return err
496 }
497 uc.logger.Printf("logged in with account %q", account)
498 case irc.RPL_LOGGEDOUT:
499 uc.logger.Printf("logged out")
500 case irc.ERR_NICKLOCKED, irc.RPL_SASLSUCCESS, irc.ERR_SASLFAIL, irc.ERR_SASLTOOLONG, irc.ERR_SASLABORTED:
501 var info string
502 if err := parseMessageParams(msg, nil, &info); err != nil {
503 return err
504 }
505 switch msg.Command {
506 case irc.ERR_NICKLOCKED:
507 uc.logger.Printf("invalid nick used with SASL authentication: %v", info)
508 case irc.ERR_SASLFAIL:
509 uc.logger.Printf("SASL authentication failed: %v", info)
510 case irc.ERR_SASLTOOLONG:
511 uc.logger.Printf("SASL message too long: %v", info)
512 }
513
514 uc.saslClient = nil
515 uc.saslStarted = false
516
517 uc.SendMessage(&irc.Message{
518 Command: "CAP",
519 Params: []string{"END"},
520 })
521 case irc.RPL_WELCOME:
522 uc.registered = true
523 uc.logger.Printf("connection registered")
524
525 uc.forEachDownstream(func(dc *downstreamConn) {
526 dc.updateSupportedCaps()
527 })
528
529 for _, ch := range uc.network.channels {
530 params := []string{ch.Name}
531 if ch.Key != "" {
532 params = append(params, ch.Key)
533 }
534 uc.SendMessage(&irc.Message{
535 Command: "JOIN",
536 Params: params,
537 })
538 }
539 case irc.RPL_MYINFO:
540 if err := parseMessageParams(msg, nil, &uc.serverName, nil, &uc.availableUserModes, nil); err != nil {
541 return err
542 }
543 case irc.RPL_ISUPPORT:
544 if err := parseMessageParams(msg, nil, nil); err != nil {
545 return err
546 }
547 for _, token := range msg.Params[1 : len(msg.Params)-1] {
548 negate := false
549 parameter := token
550 value := ""
551 if strings.HasPrefix(token, "-") {
552 negate = true
553 token = token[1:]
554 } else {
555 if i := strings.IndexByte(token, '='); i >= 0 {
556 parameter = token[:i]
557 value = token[i+1:]
558 }
559 }
560 if !negate {
561 switch parameter {
562 case "CHANMODES":
563 parts := strings.SplitN(value, ",", 5)
564 if len(parts) < 4 {
565 return fmt.Errorf("malformed ISUPPORT CHANMODES value: %v", value)
566 }
567 modes := make(map[byte]channelModeType)
568 for i, mt := range []channelModeType{modeTypeA, modeTypeB, modeTypeC, modeTypeD} {
569 for j := 0; j < len(parts[i]); j++ {
570 mode := parts[i][j]
571 modes[mode] = mt
572 }
573 }
574 uc.availableChannelModes = modes
575 case "CHANTYPES":
576 uc.availableChannelTypes = value
577 case "PREFIX":
578 if value == "" {
579 uc.availableMemberships = nil
580 } else {
581 if value[0] != '(' {
582 return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", value)
583 }
584 sep := strings.IndexByte(value, ')')
585 if sep < 0 || len(value) != sep*2 {
586 return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", value)
587 }
588 memberships := make([]membership, len(value)/2-1)
589 for i := range memberships {
590 memberships[i] = membership{
591 Mode: value[i+1],
592 Prefix: value[sep+i+1],
593 }
594 }
595 uc.availableMemberships = memberships
596 }
597 }
598 } else {
599 // TODO: handle ISUPPORT negations
600 }
601 }
602 case "BATCH":
603 var tag string
604 if err := parseMessageParams(msg, &tag); err != nil {
605 return err
606 }
607
608 if strings.HasPrefix(tag, "+") {
609 tag = tag[1:]
610 if _, ok := uc.batches[tag]; ok {
611 return fmt.Errorf("unexpected BATCH reference tag: batch was already defined: %q", tag)
612 }
613 var batchType string
614 if err := parseMessageParams(msg, nil, &batchType); err != nil {
615 return err
616 }
617 label := label
618 if label == "" && msgBatch != nil {
619 label = msgBatch.Label
620 }
621 uc.batches[tag] = batch{
622 Type: batchType,
623 Params: msg.Params[2:],
624 Outer: msgBatch,
625 Label: label,
626 }
627 } else if strings.HasPrefix(tag, "-") {
628 tag = tag[1:]
629 if _, ok := uc.batches[tag]; !ok {
630 return fmt.Errorf("unknown BATCH reference tag: %q", tag)
631 }
632 delete(uc.batches, tag)
633 } else {
634 return fmt.Errorf("unexpected BATCH reference tag: missing +/- prefix: %q", tag)
635 }
636 case "NICK":
637 if msg.Prefix == nil {
638 return fmt.Errorf("expected a prefix")
639 }
640
641 var newNick string
642 if err := parseMessageParams(msg, &newNick); err != nil {
643 return err
644 }
645
646 me := false
647 if msg.Prefix.Name == uc.nick {
648 uc.logger.Printf("changed nick from %q to %q", uc.nick, newNick)
649 me = true
650 uc.nick = newNick
651 }
652
653 for _, ch := range uc.channels {
654 if memberships, ok := ch.Members[msg.Prefix.Name]; ok {
655 delete(ch.Members, msg.Prefix.Name)
656 ch.Members[newNick] = memberships
657 uc.appendLog(ch.Name, msg)
658 uc.appendHistory(ch.Name, msg)
659 }
660 }
661
662 if !me {
663 uc.forEachDownstream(func(dc *downstreamConn) {
664 dc.SendMessage(dc.marshalMessage(msg, uc.network))
665 })
666 }
667 case "JOIN":
668 if msg.Prefix == nil {
669 return fmt.Errorf("expected a prefix")
670 }
671
672 var channels string
673 if err := parseMessageParams(msg, &channels); err != nil {
674 return err
675 }
676
677 for _, ch := range strings.Split(channels, ",") {
678 if msg.Prefix.Name == uc.nick {
679 uc.logger.Printf("joined channel %q", ch)
680 uc.channels[ch] = &upstreamChannel{
681 Name: ch,
682 conn: uc,
683 Members: make(map[string]*memberships),
684 }
685
686 uc.SendMessage(&irc.Message{
687 Command: "MODE",
688 Params: []string{ch},
689 })
690 } else {
691 ch, err := uc.getChannel(ch)
692 if err != nil {
693 return err
694 }
695 ch.Members[msg.Prefix.Name] = nil
696 }
697
698 chMsg := msg.Copy()
699 chMsg.Params[0] = ch
700 uc.produce(ch, chMsg, nil)
701 }
702 case "PART":
703 if msg.Prefix == nil {
704 return fmt.Errorf("expected a prefix")
705 }
706
707 var channels string
708 if err := parseMessageParams(msg, &channels); err != nil {
709 return err
710 }
711
712 for _, ch := range strings.Split(channels, ",") {
713 if msg.Prefix.Name == uc.nick {
714 uc.logger.Printf("parted channel %q", ch)
715 delete(uc.channels, ch)
716 } else {
717 ch, err := uc.getChannel(ch)
718 if err != nil {
719 return err
720 }
721 delete(ch.Members, msg.Prefix.Name)
722 }
723
724 chMsg := msg.Copy()
725 chMsg.Params[0] = ch
726 uc.produce(ch, chMsg, nil)
727 }
728 case "KICK":
729 if msg.Prefix == nil {
730 return fmt.Errorf("expected a prefix")
731 }
732
733 var channel, user string
734 if err := parseMessageParams(msg, &channel, &user); err != nil {
735 return err
736 }
737
738 if user == uc.nick {
739 uc.logger.Printf("kicked from channel %q by %s", channel, msg.Prefix.Name)
740 delete(uc.channels, channel)
741 } else {
742 ch, err := uc.getChannel(channel)
743 if err != nil {
744 return err
745 }
746 delete(ch.Members, user)
747 }
748
749 uc.produce(channel, msg, nil)
750 case "QUIT":
751 if msg.Prefix == nil {
752 return fmt.Errorf("expected a prefix")
753 }
754
755 if msg.Prefix.Name == uc.nick {
756 uc.logger.Printf("quit")
757 }
758
759 for _, ch := range uc.channels {
760 if _, ok := ch.Members[msg.Prefix.Name]; ok {
761 delete(ch.Members, msg.Prefix.Name)
762
763 uc.appendLog(ch.Name, msg)
764 uc.appendHistory(ch.Name, msg)
765 }
766 }
767
768 if msg.Prefix.Name != uc.nick {
769 uc.forEachDownstream(func(dc *downstreamConn) {
770 dc.SendMessage(dc.marshalMessage(msg, uc.network))
771 })
772 }
773 case irc.RPL_TOPIC, irc.RPL_NOTOPIC:
774 var name, topic string
775 if err := parseMessageParams(msg, nil, &name, &topic); err != nil {
776 return err
777 }
778 ch, err := uc.getChannel(name)
779 if err != nil {
780 return err
781 }
782 if msg.Command == irc.RPL_TOPIC {
783 ch.Topic = topic
784 } else {
785 ch.Topic = ""
786 }
787 case "TOPIC":
788 var name string
789 if err := parseMessageParams(msg, &name); err != nil {
790 return err
791 }
792 ch, err := uc.getChannel(name)
793 if err != nil {
794 return err
795 }
796 if len(msg.Params) > 1 {
797 ch.Topic = msg.Params[1]
798 } else {
799 ch.Topic = ""
800 }
801 uc.produce(ch.Name, msg, nil)
802 case "MODE":
803 var name, modeStr string
804 if err := parseMessageParams(msg, &name, &modeStr); err != nil {
805 return err
806 }
807
808 if !uc.isChannel(name) { // user mode change
809 if name != uc.nick {
810 return fmt.Errorf("received MODE message for unknown nick %q", name)
811 }
812 return uc.modes.Apply(modeStr)
813 // TODO: notify downstreams about user mode change?
814 } else { // channel mode change
815 ch, err := uc.getChannel(name)
816 if err != nil {
817 return err
818 }
819
820 needMarshaling, err := applyChannelModes(ch, modeStr, msg.Params[2:])
821 if err != nil {
822 return err
823 }
824
825 uc.appendLog(ch.Name, msg)
826 uc.forEachDownstream(func(dc *downstreamConn) {
827 params := make([]string, len(msg.Params))
828 params[0] = dc.marshalEntity(uc.network, name)
829 params[1] = modeStr
830
831 copy(params[2:], msg.Params[2:])
832 for i, modeParam := range params[2:] {
833 if _, ok := needMarshaling[i]; ok {
834 params[2+i] = dc.marshalEntity(uc.network, modeParam)
835 }
836 }
837
838 dc.SendMessage(&irc.Message{
839 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
840 Command: "MODE",
841 Params: params,
842 })
843 })
844 }
845 case irc.RPL_UMODEIS:
846 if err := parseMessageParams(msg, nil); err != nil {
847 return err
848 }
849 modeStr := ""
850 if len(msg.Params) > 1 {
851 modeStr = msg.Params[1]
852 }
853
854 uc.modes = ""
855 if err := uc.modes.Apply(modeStr); err != nil {
856 return err
857 }
858 // TODO: send RPL_UMODEIS to downstream connections when applicable
859 case irc.RPL_CHANNELMODEIS:
860 var channel string
861 if err := parseMessageParams(msg, nil, &channel); err != nil {
862 return err
863 }
864 modeStr := ""
865 if len(msg.Params) > 2 {
866 modeStr = msg.Params[2]
867 }
868
869 ch, err := uc.getChannel(channel)
870 if err != nil {
871 return err
872 }
873
874 firstMode := ch.modes == nil
875 ch.modes = make(map[byte]string)
876 if _, err := applyChannelModes(ch, modeStr, msg.Params[3:]); err != nil {
877 return err
878 }
879 if firstMode {
880 modeStr, modeParams := ch.modes.Format()
881
882 uc.forEachDownstream(func(dc *downstreamConn) {
883 params := []string{dc.nick, dc.marshalEntity(uc.network, channel), modeStr}
884 params = append(params, modeParams...)
885
886 dc.SendMessage(&irc.Message{
887 Prefix: dc.srv.prefix(),
888 Command: irc.RPL_CHANNELMODEIS,
889 Params: params,
890 })
891 })
892 }
893 case rpl_creationtime:
894 var channel, creationTime string
895 if err := parseMessageParams(msg, nil, &channel, &creationTime); err != nil {
896 return err
897 }
898
899 ch, err := uc.getChannel(channel)
900 if err != nil {
901 return err
902 }
903
904 firstCreationTime := ch.creationTime == ""
905 ch.creationTime = creationTime
906 if firstCreationTime {
907 uc.forEachDownstream(func(dc *downstreamConn) {
908 dc.SendMessage(&irc.Message{
909 Prefix: dc.srv.prefix(),
910 Command: rpl_creationtime,
911 Params: []string{dc.nick, channel, creationTime},
912 })
913 })
914 }
915 case rpl_topicwhotime:
916 var name, who, timeStr string
917 if err := parseMessageParams(msg, nil, &name, &who, &timeStr); err != nil {
918 return err
919 }
920 ch, err := uc.getChannel(name)
921 if err != nil {
922 return err
923 }
924 ch.TopicWho = who
925 sec, err := strconv.ParseInt(timeStr, 10, 64)
926 if err != nil {
927 return fmt.Errorf("failed to parse topic time: %v", err)
928 }
929 ch.TopicTime = time.Unix(sec, 0)
930 case irc.RPL_LIST:
931 var channel, clients, topic string
932 if err := parseMessageParams(msg, nil, &channel, &clients, &topic); err != nil {
933 return err
934 }
935
936 pl := uc.getPendingLIST()
937 if pl == nil {
938 return fmt.Errorf("unexpected RPL_LIST: no matching pending LIST")
939 }
940
941 uc.forEachDownstreamByID(pl.downstreamID, func(dc *downstreamConn) {
942 dc.SendMessage(&irc.Message{
943 Prefix: dc.srv.prefix(),
944 Command: irc.RPL_LIST,
945 Params: []string{dc.nick, dc.marshalEntity(uc.network, channel), clients, topic},
946 })
947 })
948 case irc.RPL_LISTEND:
949 ok := uc.endPendingLISTs(false)
950 if !ok {
951 return fmt.Errorf("unexpected RPL_LISTEND: no matching pending LIST")
952 }
953 case irc.RPL_NAMREPLY:
954 var name, statusStr, members string
955 if err := parseMessageParams(msg, nil, &statusStr, &name, &members); err != nil {
956 return err
957 }
958
959 ch, ok := uc.channels[name]
960 if !ok {
961 // NAMES on a channel we have not joined, forward to downstream
962 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
963 channel := dc.marshalEntity(uc.network, name)
964 members := splitSpace(members)
965 for i, member := range members {
966 memberships, nick := uc.parseMembershipPrefix(member)
967 members[i] = memberships.Format(dc) + dc.marshalEntity(uc.network, nick)
968 }
969 memberStr := strings.Join(members, " ")
970
971 dc.SendMessage(&irc.Message{
972 Prefix: dc.srv.prefix(),
973 Command: irc.RPL_NAMREPLY,
974 Params: []string{dc.nick, statusStr, channel, memberStr},
975 })
976 })
977 return nil
978 }
979
980 status, err := parseChannelStatus(statusStr)
981 if err != nil {
982 return err
983 }
984 ch.Status = status
985
986 for _, s := range splitSpace(members) {
987 memberships, nick := uc.parseMembershipPrefix(s)
988 ch.Members[nick] = memberships
989 }
990 case irc.RPL_ENDOFNAMES:
991 var name string
992 if err := parseMessageParams(msg, nil, &name); err != nil {
993 return err
994 }
995
996 ch, ok := uc.channels[name]
997 if !ok {
998 // NAMES on a channel we have not joined, forward to downstream
999 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1000 channel := dc.marshalEntity(uc.network, name)
1001
1002 dc.SendMessage(&irc.Message{
1003 Prefix: dc.srv.prefix(),
1004 Command: irc.RPL_ENDOFNAMES,
1005 Params: []string{dc.nick, channel, "End of /NAMES list"},
1006 })
1007 })
1008 return nil
1009 }
1010
1011 if ch.complete {
1012 return fmt.Errorf("received unexpected RPL_ENDOFNAMES")
1013 }
1014 ch.complete = true
1015
1016 uc.forEachDownstream(func(dc *downstreamConn) {
1017 forwardChannel(dc, ch)
1018 })
1019 case irc.RPL_WHOREPLY:
1020 var channel, username, host, server, nick, mode, trailing string
1021 if err := parseMessageParams(msg, nil, &channel, &username, &host, &server, &nick, &mode, &trailing); err != nil {
1022 return err
1023 }
1024
1025 parts := strings.SplitN(trailing, " ", 2)
1026 if len(parts) != 2 {
1027 return fmt.Errorf("received malformed RPL_WHOREPLY: wrong trailing parameter: %s", trailing)
1028 }
1029 realname := parts[1]
1030 hops, err := strconv.Atoi(parts[0])
1031 if err != nil {
1032 return fmt.Errorf("received malformed RPL_WHOREPLY: wrong hop count: %s", parts[0])
1033 }
1034 hops++
1035
1036 trailing = strconv.Itoa(hops) + " " + realname
1037
1038 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1039 channel := channel
1040 if channel != "*" {
1041 channel = dc.marshalEntity(uc.network, channel)
1042 }
1043 nick := dc.marshalEntity(uc.network, nick)
1044 dc.SendMessage(&irc.Message{
1045 Prefix: dc.srv.prefix(),
1046 Command: irc.RPL_WHOREPLY,
1047 Params: []string{dc.nick, channel, username, host, server, nick, mode, trailing},
1048 })
1049 })
1050 case irc.RPL_ENDOFWHO:
1051 var name string
1052 if err := parseMessageParams(msg, nil, &name); err != nil {
1053 return err
1054 }
1055
1056 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1057 name := name
1058 if name != "*" {
1059 // TODO: support WHO masks
1060 name = dc.marshalEntity(uc.network, name)
1061 }
1062 dc.SendMessage(&irc.Message{
1063 Prefix: dc.srv.prefix(),
1064 Command: irc.RPL_ENDOFWHO,
1065 Params: []string{dc.nick, name, "End of /WHO list"},
1066 })
1067 })
1068 case irc.RPL_WHOISUSER:
1069 var nick, username, host, realname string
1070 if err := parseMessageParams(msg, nil, &nick, &username, &host, nil, &realname); err != nil {
1071 return err
1072 }
1073
1074 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1075 nick := dc.marshalEntity(uc.network, nick)
1076 dc.SendMessage(&irc.Message{
1077 Prefix: dc.srv.prefix(),
1078 Command: irc.RPL_WHOISUSER,
1079 Params: []string{dc.nick, nick, username, host, "*", realname},
1080 })
1081 })
1082 case irc.RPL_WHOISSERVER:
1083 var nick, server, serverInfo string
1084 if err := parseMessageParams(msg, nil, &nick, &server, &serverInfo); err != nil {
1085 return err
1086 }
1087
1088 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1089 nick := dc.marshalEntity(uc.network, nick)
1090 dc.SendMessage(&irc.Message{
1091 Prefix: dc.srv.prefix(),
1092 Command: irc.RPL_WHOISSERVER,
1093 Params: []string{dc.nick, nick, server, serverInfo},
1094 })
1095 })
1096 case irc.RPL_WHOISOPERATOR:
1097 var nick string
1098 if err := parseMessageParams(msg, nil, &nick); err != nil {
1099 return err
1100 }
1101
1102 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1103 nick := dc.marshalEntity(uc.network, nick)
1104 dc.SendMessage(&irc.Message{
1105 Prefix: dc.srv.prefix(),
1106 Command: irc.RPL_WHOISOPERATOR,
1107 Params: []string{dc.nick, nick, "is an IRC operator"},
1108 })
1109 })
1110 case irc.RPL_WHOISIDLE:
1111 var nick string
1112 if err := parseMessageParams(msg, nil, &nick, nil); err != nil {
1113 return err
1114 }
1115
1116 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1117 nick := dc.marshalEntity(uc.network, nick)
1118 params := []string{dc.nick, nick}
1119 params = append(params, msg.Params[2:]...)
1120 dc.SendMessage(&irc.Message{
1121 Prefix: dc.srv.prefix(),
1122 Command: irc.RPL_WHOISIDLE,
1123 Params: params,
1124 })
1125 })
1126 case irc.RPL_WHOISCHANNELS:
1127 var nick, channelList string
1128 if err := parseMessageParams(msg, nil, &nick, &channelList); err != nil {
1129 return err
1130 }
1131 channels := splitSpace(channelList)
1132
1133 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1134 nick := dc.marshalEntity(uc.network, nick)
1135 channelList := make([]string, len(channels))
1136 for i, channel := range channels {
1137 prefix, channel := uc.parseMembershipPrefix(channel)
1138 channel = dc.marshalEntity(uc.network, channel)
1139 channelList[i] = prefix.Format(dc) + channel
1140 }
1141 channels := strings.Join(channelList, " ")
1142 dc.SendMessage(&irc.Message{
1143 Prefix: dc.srv.prefix(),
1144 Command: irc.RPL_WHOISCHANNELS,
1145 Params: []string{dc.nick, nick, channels},
1146 })
1147 })
1148 case irc.RPL_ENDOFWHOIS:
1149 var nick string
1150 if err := parseMessageParams(msg, nil, &nick); err != nil {
1151 return err
1152 }
1153
1154 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1155 nick := dc.marshalEntity(uc.network, nick)
1156 dc.SendMessage(&irc.Message{
1157 Prefix: dc.srv.prefix(),
1158 Command: irc.RPL_ENDOFWHOIS,
1159 Params: []string{dc.nick, nick, "End of /WHOIS list"},
1160 })
1161 })
1162 case "INVITE":
1163 var nick, channel string
1164 if err := parseMessageParams(msg, &nick, &channel); err != nil {
1165 return err
1166 }
1167
1168 uc.forEachDownstream(func(dc *downstreamConn) {
1169 dc.SendMessage(&irc.Message{
1170 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
1171 Command: "INVITE",
1172 Params: []string{dc.marshalEntity(uc.network, nick), dc.marshalEntity(uc.network, channel)},
1173 })
1174 })
1175 case irc.RPL_INVITING:
1176 var nick, channel string
1177 if err := parseMessageParams(msg, &nick, &channel); err != nil {
1178 return err
1179 }
1180
1181 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1182 dc.SendMessage(&irc.Message{
1183 Prefix: dc.srv.prefix(),
1184 Command: irc.RPL_INVITING,
1185 Params: []string{dc.nick, dc.marshalEntity(uc.network, nick), dc.marshalEntity(uc.network, channel)},
1186 })
1187 })
1188 case irc.ERR_UNKNOWNCOMMAND, irc.RPL_TRYAGAIN:
1189 var command, reason string
1190 if err := parseMessageParams(msg, nil, &command, &reason); err != nil {
1191 return err
1192 }
1193
1194 if command == "LIST" {
1195 ok := uc.endPendingLISTs(false)
1196 if !ok {
1197 return fmt.Errorf("unexpected response for LIST: %q: no matching pending LIST", msg.Command)
1198 }
1199 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1200 dc.SendMessage(&irc.Message{
1201 Prefix: uc.srv.prefix(),
1202 Command: msg.Command,
1203 Params: []string{dc.nick, "LIST", reason},
1204 })
1205 })
1206 }
1207 case irc.RPL_AWAY:
1208 var nick, reason string
1209 if err := parseMessageParams(msg, nil, &nick, &reason); err != nil {
1210 return err
1211 }
1212
1213 uc.forEachDownstream(func(dc *downstreamConn) {
1214 dc.SendMessage(&irc.Message{
1215 Prefix: dc.srv.prefix(),
1216 Command: irc.RPL_AWAY,
1217 Params: []string{dc.nick, dc.marshalEntity(uc.network, nick), reason},
1218 })
1219 })
1220 case "AWAY":
1221 if msg.Prefix == nil {
1222 return fmt.Errorf("expected a prefix")
1223 }
1224
1225 uc.forEachDownstream(func(dc *downstreamConn) {
1226 if !dc.caps["away-notify"] {
1227 return
1228 }
1229 dc.SendMessage(&irc.Message{
1230 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
1231 Command: "AWAY",
1232 Params: msg.Params,
1233 })
1234 })
1235 case "TAGMSG":
1236 // TODO: relay to downstream connections that accept message-tags
1237 case "ACK":
1238 // Ignore
1239 case irc.RPL_NOWAWAY, irc.RPL_UNAWAY:
1240 // Ignore
1241 case irc.RPL_YOURHOST, irc.RPL_CREATED:
1242 // Ignore
1243 case irc.RPL_LUSERCLIENT, irc.RPL_LUSEROP, irc.RPL_LUSERUNKNOWN, irc.RPL_LUSERCHANNELS, irc.RPL_LUSERME:
1244 // Ignore
1245 case irc.RPL_MOTDSTART, irc.RPL_MOTD, irc.RPL_ENDOFMOTD:
1246 // Ignore
1247 case irc.RPL_LISTSTART:
1248 // Ignore
1249 case rpl_localusers, rpl_globalusers:
1250 // Ignore
1251 case irc.RPL_STATSVLINE, rpl_statsping, irc.RPL_STATSBLINE, irc.RPL_STATSDLINE:
1252 // Ignore
1253 default:
1254 uc.logger.Printf("unhandled message: %v", msg)
1255 }
1256 return nil
1257}
1258
1259func (uc *upstreamConn) handleSupportedCaps(capsStr string) {
1260 caps := strings.Fields(capsStr)
1261 for _, s := range caps {
1262 kv := strings.SplitN(s, "=", 2)
1263 k := strings.ToLower(kv[0])
1264 var v string
1265 if len(kv) == 2 {
1266 v = kv[1]
1267 }
1268 uc.supportedCaps[k] = v
1269 }
1270}
1271
1272func (uc *upstreamConn) requestCaps() {
1273 var requestCaps []string
1274 for c := range permanentUpstreamCaps {
1275 if _, ok := uc.supportedCaps[c]; ok && !uc.caps[c] {
1276 requestCaps = append(requestCaps, c)
1277 }
1278 }
1279
1280 if uc.requestSASL() && !uc.caps["sasl"] {
1281 requestCaps = append(requestCaps, "sasl")
1282 }
1283
1284 if len(requestCaps) == 0 {
1285 return
1286 }
1287
1288 uc.SendMessage(&irc.Message{
1289 Command: "CAP",
1290 Params: []string{"REQ", strings.Join(requestCaps, " ")},
1291 })
1292}
1293
1294func (uc *upstreamConn) requestSASL() bool {
1295 if uc.network.SASL.Mechanism == "" {
1296 return false
1297 }
1298
1299 v, ok := uc.supportedCaps["sasl"]
1300 if !ok {
1301 return false
1302 }
1303 if v != "" {
1304 mechanisms := strings.Split(v, ",")
1305 found := false
1306 for _, mech := range mechanisms {
1307 if strings.EqualFold(mech, uc.network.SASL.Mechanism) {
1308 found = true
1309 break
1310 }
1311 }
1312 if !found {
1313 return false
1314 }
1315 }
1316
1317 return true
1318}
1319
1320func (uc *upstreamConn) handleCapAck(name string, ok bool) error {
1321 uc.caps[name] = ok
1322
1323 switch name {
1324 case "sasl":
1325 if !ok {
1326 uc.logger.Printf("server refused to acknowledge the SASL capability")
1327 return nil
1328 }
1329
1330 auth := &uc.network.SASL
1331 switch auth.Mechanism {
1332 case "PLAIN":
1333 uc.logger.Printf("starting SASL PLAIN authentication with username %q", auth.Plain.Username)
1334 uc.saslClient = sasl.NewPlainClient("", auth.Plain.Username, auth.Plain.Password)
1335 default:
1336 return fmt.Errorf("unsupported SASL mechanism %q", name)
1337 }
1338
1339 uc.SendMessage(&irc.Message{
1340 Command: "AUTHENTICATE",
1341 Params: []string{auth.Mechanism},
1342 })
1343 default:
1344 if permanentUpstreamCaps[name] {
1345 break
1346 }
1347 uc.logger.Printf("received CAP ACK/NAK for a cap we don't support: %v", name)
1348 }
1349 return nil
1350}
1351
1352func splitSpace(s string) []string {
1353 return strings.FieldsFunc(s, func(r rune) bool {
1354 return r == ' '
1355 })
1356}
1357
1358func (uc *upstreamConn) register() {
1359 uc.nick = uc.network.Nick
1360 uc.username = uc.network.Username
1361 if uc.username == "" {
1362 uc.username = uc.nick
1363 }
1364 uc.realname = uc.network.Realname
1365 if uc.realname == "" {
1366 uc.realname = uc.nick
1367 }
1368
1369 uc.SendMessage(&irc.Message{
1370 Command: "CAP",
1371 Params: []string{"LS", "302"},
1372 })
1373
1374 if uc.network.Pass != "" {
1375 uc.SendMessage(&irc.Message{
1376 Command: "PASS",
1377 Params: []string{uc.network.Pass},
1378 })
1379 }
1380
1381 uc.SendMessage(&irc.Message{
1382 Command: "NICK",
1383 Params: []string{uc.nick},
1384 })
1385 uc.SendMessage(&irc.Message{
1386 Command: "USER",
1387 Params: []string{uc.username, "0", "*", uc.realname},
1388 })
1389}
1390
1391func (uc *upstreamConn) runUntilRegistered() error {
1392 for !uc.registered {
1393 msg, err := uc.ReadMessage()
1394 if err != nil {
1395 return fmt.Errorf("failed to read message: %v", err)
1396 }
1397
1398 if err := uc.handleMessage(msg); err != nil {
1399 return fmt.Errorf("failed to handle message %q: %v", msg, err)
1400 }
1401 }
1402
1403 for _, command := range uc.network.ConnectCommands {
1404 m, err := irc.ParseMessage(command)
1405 if err != nil {
1406 uc.logger.Printf("failed to parse connect command %q: %v", command, err)
1407 } else {
1408 uc.SendMessage(m)
1409 }
1410 }
1411
1412 return nil
1413}
1414
1415func (uc *upstreamConn) readMessages(ch chan<- event) error {
1416 for {
1417 msg, err := uc.ReadMessage()
1418 if err == io.EOF {
1419 break
1420 } else if err != nil {
1421 return fmt.Errorf("failed to read IRC command: %v", err)
1422 }
1423
1424 ch <- eventUpstreamMessage{msg, uc}
1425 }
1426
1427 return nil
1428}
1429
1430func (uc *upstreamConn) SendMessageLabeled(downstreamID uint64, msg *irc.Message) {
1431 if uc.caps["labeled-response"] {
1432 if msg.Tags == nil {
1433 msg.Tags = make(map[string]irc.TagValue)
1434 }
1435 msg.Tags["label"] = irc.TagValue(fmt.Sprintf("sd-%d-%d", downstreamID, uc.nextLabelID))
1436 uc.nextLabelID++
1437 }
1438 uc.SendMessage(msg)
1439}
1440
1441// TODO: handle moving logs when a network name changes, when support for this is added
1442func (uc *upstreamConn) appendLog(entity string, msg *irc.Message) {
1443 if uc.srv.LogPath == "" {
1444 return
1445 }
1446
1447 ml, ok := uc.messageLoggers[entity]
1448 if !ok {
1449 ml = newMessageLogger(uc.network, entity)
1450 uc.messageLoggers[entity] = ml
1451 }
1452
1453 if err := ml.Append(msg); err != nil {
1454 uc.logger.Printf("failed to log message: %v", err)
1455 }
1456}
1457
1458// appendHistory appends a message to the history. entity can be empty.
1459func (uc *upstreamConn) appendHistory(entity string, msg *irc.Message) {
1460 detached := false
1461 if ch, ok := uc.network.channels[entity]; ok {
1462 detached = ch.Detached
1463 }
1464
1465 // If no client is offline, no need to append the message to the buffer
1466 if len(uc.network.offlineClients) == 0 && !detached {
1467 return
1468 }
1469
1470 history, ok := uc.network.history[entity]
1471 if !ok {
1472 history = &networkHistory{
1473 offlineClients: make(map[string]uint64),
1474 ring: NewRing(uc.srv.RingCap),
1475 }
1476 uc.network.history[entity] = history
1477
1478 for clientName, _ := range uc.network.offlineClients {
1479 history.offlineClients[clientName] = 0
1480 }
1481
1482 if detached {
1483 // If the channel is detached, online clients act as offline
1484 // clients too
1485 uc.forEachDownstream(func(dc *downstreamConn) {
1486 history.offlineClients[dc.clientName] = 0
1487 })
1488 }
1489 }
1490
1491 history.ring.Produce(msg)
1492}
1493
1494// produce appends a message to the logs, adds it to the history and forwards
1495// it to connected downstream connections.
1496//
1497// If origin is not nil and origin doesn't support echo-message, the message is
1498// forwarded to all connections except origin.
1499func (uc *upstreamConn) produce(target string, msg *irc.Message, origin *downstreamConn) {
1500 if target != "" {
1501 uc.appendLog(target, msg)
1502 }
1503
1504 uc.appendHistory(target, msg)
1505
1506 // Don't forward messages if it's a detached channel
1507 if ch, ok := uc.network.channels[target]; ok && ch.Detached {
1508 return
1509 }
1510
1511 uc.forEachDownstream(func(dc *downstreamConn) {
1512 if dc != origin || dc.caps["echo-message"] {
1513 dc.SendMessage(dc.marshalMessage(msg, uc.network))
1514 }
1515 })
1516}
1517
1518func (uc *upstreamConn) updateAway() {
1519 away := true
1520 uc.forEachDownstream(func(*downstreamConn) {
1521 away = false
1522 })
1523 if away == uc.away {
1524 return
1525 }
1526 if away {
1527 uc.SendMessage(&irc.Message{
1528 Command: "AWAY",
1529 Params: []string{"Auto away"},
1530 })
1531 } else {
1532 uc.SendMessage(&irc.Message{
1533 Command: "AWAY",
1534 })
1535 }
1536 uc.away = away
1537}
Note: See TracBrowser for help on using the repository browser.