source: code/trunk/upstream.go@ 300

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

Add support for upstream ban, invite, and exception lists

This does not try to marshal nicks in masks, for simplicity and
consistency with the current behaviour of marshaling MODE messages.

File size: 38.9 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.registered {
402 uc.forEachDownstream(func(dc *downstreamConn) {
403 dc.updateSupportedCaps()
404 })
405 }
406 case "NEW":
407 if len(subParams) < 1 {
408 return newNeedMoreParamsError(msg.Command)
409 }
410 uc.handleSupportedCaps(subParams[0])
411 uc.requestCaps()
412 case "DEL":
413 if len(subParams) < 1 {
414 return newNeedMoreParamsError(msg.Command)
415 }
416 caps := strings.Fields(subParams[0])
417
418 for _, c := range caps {
419 delete(uc.supportedCaps, c)
420 delete(uc.caps, c)
421 }
422
423 if uc.registered {
424 uc.forEachDownstream(func(dc *downstreamConn) {
425 dc.updateSupportedCaps()
426 })
427 }
428 default:
429 uc.logger.Printf("unhandled message: %v", msg)
430 }
431 case "AUTHENTICATE":
432 if uc.saslClient == nil {
433 return fmt.Errorf("received unexpected AUTHENTICATE message")
434 }
435
436 // TODO: if a challenge is 400 bytes long, buffer it
437 var challengeStr string
438 if err := parseMessageParams(msg, &challengeStr); err != nil {
439 uc.SendMessage(&irc.Message{
440 Command: "AUTHENTICATE",
441 Params: []string{"*"},
442 })
443 return err
444 }
445
446 var challenge []byte
447 if challengeStr != "+" {
448 var err error
449 challenge, err = base64.StdEncoding.DecodeString(challengeStr)
450 if err != nil {
451 uc.SendMessage(&irc.Message{
452 Command: "AUTHENTICATE",
453 Params: []string{"*"},
454 })
455 return err
456 }
457 }
458
459 var resp []byte
460 var err error
461 if !uc.saslStarted {
462 _, resp, err = uc.saslClient.Start()
463 uc.saslStarted = true
464 } else {
465 resp, err = uc.saslClient.Next(challenge)
466 }
467 if err != nil {
468 uc.SendMessage(&irc.Message{
469 Command: "AUTHENTICATE",
470 Params: []string{"*"},
471 })
472 return err
473 }
474
475 // TODO: send response in multiple chunks if >= 400 bytes
476 var respStr = "+"
477 if resp != nil {
478 respStr = base64.StdEncoding.EncodeToString(resp)
479 }
480
481 uc.SendMessage(&irc.Message{
482 Command: "AUTHENTICATE",
483 Params: []string{respStr},
484 })
485 case irc.RPL_LOGGEDIN:
486 var account string
487 if err := parseMessageParams(msg, nil, nil, &account); err != nil {
488 return err
489 }
490 uc.logger.Printf("logged in with account %q", account)
491 case irc.RPL_LOGGEDOUT:
492 uc.logger.Printf("logged out")
493 case irc.ERR_NICKLOCKED, irc.RPL_SASLSUCCESS, irc.ERR_SASLFAIL, irc.ERR_SASLTOOLONG, irc.ERR_SASLABORTED:
494 var info string
495 if err := parseMessageParams(msg, nil, &info); err != nil {
496 return err
497 }
498 switch msg.Command {
499 case irc.ERR_NICKLOCKED:
500 uc.logger.Printf("invalid nick used with SASL authentication: %v", info)
501 case irc.ERR_SASLFAIL:
502 uc.logger.Printf("SASL authentication failed: %v", info)
503 case irc.ERR_SASLTOOLONG:
504 uc.logger.Printf("SASL message too long: %v", info)
505 }
506
507 uc.saslClient = nil
508 uc.saslStarted = false
509
510 uc.SendMessage(&irc.Message{
511 Command: "CAP",
512 Params: []string{"END"},
513 })
514 case irc.RPL_WELCOME:
515 uc.registered = true
516 uc.logger.Printf("connection registered")
517
518 uc.forEachDownstream(func(dc *downstreamConn) {
519 dc.updateSupportedCaps()
520 })
521
522 for _, ch := range uc.network.channels {
523 params := []string{ch.Name}
524 if ch.Key != "" {
525 params = append(params, ch.Key)
526 }
527 uc.SendMessage(&irc.Message{
528 Command: "JOIN",
529 Params: params,
530 })
531 }
532 case irc.RPL_MYINFO:
533 if err := parseMessageParams(msg, nil, &uc.serverName, nil, &uc.availableUserModes, nil); err != nil {
534 return err
535 }
536 case irc.RPL_ISUPPORT:
537 if err := parseMessageParams(msg, nil, nil); err != nil {
538 return err
539 }
540 for _, token := range msg.Params[1 : len(msg.Params)-1] {
541 negate := false
542 parameter := token
543 value := ""
544 if strings.HasPrefix(token, "-") {
545 negate = true
546 token = token[1:]
547 } else {
548 if i := strings.IndexByte(token, '='); i >= 0 {
549 parameter = token[:i]
550 value = token[i+1:]
551 }
552 }
553 if !negate {
554 switch parameter {
555 case "CHANMODES":
556 parts := strings.SplitN(value, ",", 5)
557 if len(parts) < 4 {
558 return fmt.Errorf("malformed ISUPPORT CHANMODES value: %v", value)
559 }
560 modes := make(map[byte]channelModeType)
561 for i, mt := range []channelModeType{modeTypeA, modeTypeB, modeTypeC, modeTypeD} {
562 for j := 0; j < len(parts[i]); j++ {
563 mode := parts[i][j]
564 modes[mode] = mt
565 }
566 }
567 uc.availableChannelModes = modes
568 case "CHANTYPES":
569 uc.availableChannelTypes = value
570 case "PREFIX":
571 if value == "" {
572 uc.availableMemberships = nil
573 } else {
574 if value[0] != '(' {
575 return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", value)
576 }
577 sep := strings.IndexByte(value, ')')
578 if sep < 0 || len(value) != sep*2 {
579 return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", value)
580 }
581 memberships := make([]membership, len(value)/2-1)
582 for i := range memberships {
583 memberships[i] = membership{
584 Mode: value[i+1],
585 Prefix: value[sep+i+1],
586 }
587 }
588 uc.availableMemberships = memberships
589 }
590 }
591 } else {
592 // TODO: handle ISUPPORT negations
593 }
594 }
595 case "BATCH":
596 var tag string
597 if err := parseMessageParams(msg, &tag); err != nil {
598 return err
599 }
600
601 if strings.HasPrefix(tag, "+") {
602 tag = tag[1:]
603 if _, ok := uc.batches[tag]; ok {
604 return fmt.Errorf("unexpected BATCH reference tag: batch was already defined: %q", tag)
605 }
606 var batchType string
607 if err := parseMessageParams(msg, nil, &batchType); err != nil {
608 return err
609 }
610 label := label
611 if label == "" && msgBatch != nil {
612 label = msgBatch.Label
613 }
614 uc.batches[tag] = batch{
615 Type: batchType,
616 Params: msg.Params[2:],
617 Outer: msgBatch,
618 Label: label,
619 }
620 } else if strings.HasPrefix(tag, "-") {
621 tag = tag[1:]
622 if _, ok := uc.batches[tag]; !ok {
623 return fmt.Errorf("unknown BATCH reference tag: %q", tag)
624 }
625 delete(uc.batches, tag)
626 } else {
627 return fmt.Errorf("unexpected BATCH reference tag: missing +/- prefix: %q", tag)
628 }
629 case "NICK":
630 if msg.Prefix == nil {
631 return fmt.Errorf("expected a prefix")
632 }
633
634 var newNick string
635 if err := parseMessageParams(msg, &newNick); err != nil {
636 return err
637 }
638
639 me := false
640 if msg.Prefix.Name == uc.nick {
641 uc.logger.Printf("changed nick from %q to %q", uc.nick, newNick)
642 me = true
643 uc.nick = newNick
644 }
645
646 for _, ch := range uc.channels {
647 if memberships, ok := ch.Members[msg.Prefix.Name]; ok {
648 delete(ch.Members, msg.Prefix.Name)
649 ch.Members[newNick] = memberships
650 uc.appendLog(ch.Name, msg)
651 uc.appendHistory(ch.Name, msg)
652 }
653 }
654
655 if !me {
656 uc.forEachDownstream(func(dc *downstreamConn) {
657 dc.SendMessage(dc.marshalMessage(msg, uc.network))
658 })
659 } else {
660 uc.forEachDownstream(func(dc *downstreamConn) {
661 dc.updateNick()
662 })
663 }
664 case "JOIN":
665 if msg.Prefix == nil {
666 return fmt.Errorf("expected a prefix")
667 }
668
669 var channels string
670 if err := parseMessageParams(msg, &channels); err != nil {
671 return err
672 }
673
674 for _, ch := range strings.Split(channels, ",") {
675 if msg.Prefix.Name == uc.nick {
676 uc.logger.Printf("joined channel %q", ch)
677 uc.channels[ch] = &upstreamChannel{
678 Name: ch,
679 conn: uc,
680 Members: make(map[string]*memberships),
681 }
682
683 uc.SendMessage(&irc.Message{
684 Command: "MODE",
685 Params: []string{ch},
686 })
687 } else {
688 ch, err := uc.getChannel(ch)
689 if err != nil {
690 return err
691 }
692 ch.Members[msg.Prefix.Name] = &memberships{}
693 }
694
695 chMsg := msg.Copy()
696 chMsg.Params[0] = ch
697 uc.produce(ch, chMsg, nil)
698 }
699 case "PART":
700 if msg.Prefix == nil {
701 return fmt.Errorf("expected a prefix")
702 }
703
704 var channels string
705 if err := parseMessageParams(msg, &channels); err != nil {
706 return err
707 }
708
709 for _, ch := range strings.Split(channels, ",") {
710 if msg.Prefix.Name == uc.nick {
711 uc.logger.Printf("parted channel %q", ch)
712 delete(uc.channels, ch)
713 } else {
714 ch, err := uc.getChannel(ch)
715 if err != nil {
716 return err
717 }
718 delete(ch.Members, msg.Prefix.Name)
719 }
720
721 chMsg := msg.Copy()
722 chMsg.Params[0] = ch
723 uc.produce(ch, chMsg, nil)
724 }
725 case "KICK":
726 if msg.Prefix == nil {
727 return fmt.Errorf("expected a prefix")
728 }
729
730 var channel, user string
731 if err := parseMessageParams(msg, &channel, &user); err != nil {
732 return err
733 }
734
735 if user == uc.nick {
736 uc.logger.Printf("kicked from channel %q by %s", channel, msg.Prefix.Name)
737 delete(uc.channels, channel)
738 } else {
739 ch, err := uc.getChannel(channel)
740 if err != nil {
741 return err
742 }
743 delete(ch.Members, user)
744 }
745
746 uc.produce(channel, msg, nil)
747 case "QUIT":
748 if msg.Prefix == nil {
749 return fmt.Errorf("expected a prefix")
750 }
751
752 if msg.Prefix.Name == uc.nick {
753 uc.logger.Printf("quit")
754 }
755
756 for _, ch := range uc.channels {
757 if _, ok := ch.Members[msg.Prefix.Name]; ok {
758 delete(ch.Members, msg.Prefix.Name)
759
760 uc.appendLog(ch.Name, msg)
761 uc.appendHistory(ch.Name, msg)
762 }
763 }
764
765 if msg.Prefix.Name != uc.nick {
766 uc.forEachDownstream(func(dc *downstreamConn) {
767 dc.SendMessage(dc.marshalMessage(msg, uc.network))
768 })
769 }
770 case irc.RPL_TOPIC, irc.RPL_NOTOPIC:
771 var name, topic string
772 if err := parseMessageParams(msg, nil, &name, &topic); err != nil {
773 return err
774 }
775 ch, err := uc.getChannel(name)
776 if err != nil {
777 return err
778 }
779 if msg.Command == irc.RPL_TOPIC {
780 ch.Topic = topic
781 } else {
782 ch.Topic = ""
783 }
784 case "TOPIC":
785 var name string
786 if err := parseMessageParams(msg, &name); err != nil {
787 return err
788 }
789 ch, err := uc.getChannel(name)
790 if err != nil {
791 return err
792 }
793 if len(msg.Params) > 1 {
794 ch.Topic = msg.Params[1]
795 } else {
796 ch.Topic = ""
797 }
798 uc.produce(ch.Name, msg, nil)
799 case "MODE":
800 var name, modeStr string
801 if err := parseMessageParams(msg, &name, &modeStr); err != nil {
802 return err
803 }
804
805 if !uc.isChannel(name) { // user mode change
806 if name != uc.nick {
807 return fmt.Errorf("received MODE message for unknown nick %q", name)
808 }
809 return uc.modes.Apply(modeStr)
810 // TODO: notify downstreams about user mode change?
811 } else { // channel mode change
812 ch, err := uc.getChannel(name)
813 if err != nil {
814 return err
815 }
816
817 needMarshaling, err := applyChannelModes(ch, modeStr, msg.Params[2:])
818 if err != nil {
819 return err
820 }
821
822 uc.appendLog(ch.Name, msg)
823 uc.forEachDownstream(func(dc *downstreamConn) {
824 params := make([]string, len(msg.Params))
825 params[0] = dc.marshalEntity(uc.network, name)
826 params[1] = modeStr
827
828 copy(params[2:], msg.Params[2:])
829 for i, modeParam := range params[2:] {
830 if _, ok := needMarshaling[i]; ok {
831 params[2+i] = dc.marshalEntity(uc.network, modeParam)
832 }
833 }
834
835 dc.SendMessage(&irc.Message{
836 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
837 Command: "MODE",
838 Params: params,
839 })
840 })
841 }
842 case irc.RPL_UMODEIS:
843 if err := parseMessageParams(msg, nil); err != nil {
844 return err
845 }
846 modeStr := ""
847 if len(msg.Params) > 1 {
848 modeStr = msg.Params[1]
849 }
850
851 uc.modes = ""
852 if err := uc.modes.Apply(modeStr); err != nil {
853 return err
854 }
855 // TODO: send RPL_UMODEIS to downstream connections when applicable
856 case irc.RPL_CHANNELMODEIS:
857 var channel string
858 if err := parseMessageParams(msg, nil, &channel); err != nil {
859 return err
860 }
861 modeStr := ""
862 if len(msg.Params) > 2 {
863 modeStr = msg.Params[2]
864 }
865
866 ch, err := uc.getChannel(channel)
867 if err != nil {
868 return err
869 }
870
871 firstMode := ch.modes == nil
872 ch.modes = make(map[byte]string)
873 if _, err := applyChannelModes(ch, modeStr, msg.Params[3:]); err != nil {
874 return err
875 }
876 if firstMode {
877 modeStr, modeParams := ch.modes.Format()
878
879 uc.forEachDownstream(func(dc *downstreamConn) {
880 params := []string{dc.nick, dc.marshalEntity(uc.network, channel), modeStr}
881 params = append(params, modeParams...)
882
883 dc.SendMessage(&irc.Message{
884 Prefix: dc.srv.prefix(),
885 Command: irc.RPL_CHANNELMODEIS,
886 Params: params,
887 })
888 })
889 }
890 case rpl_creationtime:
891 var channel, creationTime string
892 if err := parseMessageParams(msg, nil, &channel, &creationTime); err != nil {
893 return err
894 }
895
896 ch, err := uc.getChannel(channel)
897 if err != nil {
898 return err
899 }
900
901 firstCreationTime := ch.creationTime == ""
902 ch.creationTime = creationTime
903 if firstCreationTime {
904 uc.forEachDownstream(func(dc *downstreamConn) {
905 dc.SendMessage(&irc.Message{
906 Prefix: dc.srv.prefix(),
907 Command: rpl_creationtime,
908 Params: []string{dc.nick, channel, creationTime},
909 })
910 })
911 }
912 case rpl_topicwhotime:
913 var name, who, timeStr string
914 if err := parseMessageParams(msg, nil, &name, &who, &timeStr); err != nil {
915 return err
916 }
917 ch, err := uc.getChannel(name)
918 if err != nil {
919 return err
920 }
921 ch.TopicWho = who
922 sec, err := strconv.ParseInt(timeStr, 10, 64)
923 if err != nil {
924 return fmt.Errorf("failed to parse topic time: %v", err)
925 }
926 ch.TopicTime = time.Unix(sec, 0)
927 case irc.RPL_LIST:
928 var channel, clients, topic string
929 if err := parseMessageParams(msg, nil, &channel, &clients, &topic); err != nil {
930 return err
931 }
932
933 pl := uc.getPendingLIST()
934 if pl == nil {
935 return fmt.Errorf("unexpected RPL_LIST: no matching pending LIST")
936 }
937
938 uc.forEachDownstreamByID(pl.downstreamID, func(dc *downstreamConn) {
939 dc.SendMessage(&irc.Message{
940 Prefix: dc.srv.prefix(),
941 Command: irc.RPL_LIST,
942 Params: []string{dc.nick, dc.marshalEntity(uc.network, channel), clients, topic},
943 })
944 })
945 case irc.RPL_LISTEND:
946 ok := uc.endPendingLISTs(false)
947 if !ok {
948 return fmt.Errorf("unexpected RPL_LISTEND: no matching pending LIST")
949 }
950 case irc.RPL_NAMREPLY:
951 var name, statusStr, members string
952 if err := parseMessageParams(msg, nil, &statusStr, &name, &members); err != nil {
953 return err
954 }
955
956 ch, ok := uc.channels[name]
957 if !ok {
958 // NAMES on a channel we have not joined, forward to downstream
959 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
960 channel := dc.marshalEntity(uc.network, name)
961 members := splitSpace(members)
962 for i, member := range members {
963 memberships, nick := uc.parseMembershipPrefix(member)
964 members[i] = memberships.Format(dc) + dc.marshalEntity(uc.network, nick)
965 }
966 memberStr := strings.Join(members, " ")
967
968 dc.SendMessage(&irc.Message{
969 Prefix: dc.srv.prefix(),
970 Command: irc.RPL_NAMREPLY,
971 Params: []string{dc.nick, statusStr, channel, memberStr},
972 })
973 })
974 return nil
975 }
976
977 status, err := parseChannelStatus(statusStr)
978 if err != nil {
979 return err
980 }
981 ch.Status = status
982
983 for _, s := range splitSpace(members) {
984 memberships, nick := uc.parseMembershipPrefix(s)
985 ch.Members[nick] = memberships
986 }
987 case irc.RPL_ENDOFNAMES:
988 var name string
989 if err := parseMessageParams(msg, nil, &name); err != nil {
990 return err
991 }
992
993 ch, ok := uc.channels[name]
994 if !ok {
995 // NAMES on a channel we have not joined, forward to downstream
996 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
997 channel := dc.marshalEntity(uc.network, name)
998
999 dc.SendMessage(&irc.Message{
1000 Prefix: dc.srv.prefix(),
1001 Command: irc.RPL_ENDOFNAMES,
1002 Params: []string{dc.nick, channel, "End of /NAMES list"},
1003 })
1004 })
1005 return nil
1006 }
1007
1008 if ch.complete {
1009 return fmt.Errorf("received unexpected RPL_ENDOFNAMES")
1010 }
1011 ch.complete = true
1012
1013 uc.forEachDownstream(func(dc *downstreamConn) {
1014 forwardChannel(dc, ch)
1015 })
1016 case irc.RPL_WHOREPLY:
1017 var channel, username, host, server, nick, mode, trailing string
1018 if err := parseMessageParams(msg, nil, &channel, &username, &host, &server, &nick, &mode, &trailing); err != nil {
1019 return err
1020 }
1021
1022 parts := strings.SplitN(trailing, " ", 2)
1023 if len(parts) != 2 {
1024 return fmt.Errorf("received malformed RPL_WHOREPLY: wrong trailing parameter: %s", trailing)
1025 }
1026 realname := parts[1]
1027 hops, err := strconv.Atoi(parts[0])
1028 if err != nil {
1029 return fmt.Errorf("received malformed RPL_WHOREPLY: wrong hop count: %s", parts[0])
1030 }
1031 hops++
1032
1033 trailing = strconv.Itoa(hops) + " " + realname
1034
1035 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1036 channel := channel
1037 if channel != "*" {
1038 channel = dc.marshalEntity(uc.network, channel)
1039 }
1040 nick := dc.marshalEntity(uc.network, nick)
1041 dc.SendMessage(&irc.Message{
1042 Prefix: dc.srv.prefix(),
1043 Command: irc.RPL_WHOREPLY,
1044 Params: []string{dc.nick, channel, username, host, server, nick, mode, trailing},
1045 })
1046 })
1047 case irc.RPL_ENDOFWHO:
1048 var name string
1049 if err := parseMessageParams(msg, nil, &name); err != nil {
1050 return err
1051 }
1052
1053 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1054 name := name
1055 if name != "*" {
1056 // TODO: support WHO masks
1057 name = dc.marshalEntity(uc.network, name)
1058 }
1059 dc.SendMessage(&irc.Message{
1060 Prefix: dc.srv.prefix(),
1061 Command: irc.RPL_ENDOFWHO,
1062 Params: []string{dc.nick, name, "End of /WHO list"},
1063 })
1064 })
1065 case irc.RPL_WHOISUSER:
1066 var nick, username, host, realname string
1067 if err := parseMessageParams(msg, nil, &nick, &username, &host, nil, &realname); err != nil {
1068 return err
1069 }
1070
1071 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1072 nick := dc.marshalEntity(uc.network, nick)
1073 dc.SendMessage(&irc.Message{
1074 Prefix: dc.srv.prefix(),
1075 Command: irc.RPL_WHOISUSER,
1076 Params: []string{dc.nick, nick, username, host, "*", realname},
1077 })
1078 })
1079 case irc.RPL_WHOISSERVER:
1080 var nick, server, serverInfo string
1081 if err := parseMessageParams(msg, nil, &nick, &server, &serverInfo); err != nil {
1082 return err
1083 }
1084
1085 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1086 nick := dc.marshalEntity(uc.network, nick)
1087 dc.SendMessage(&irc.Message{
1088 Prefix: dc.srv.prefix(),
1089 Command: irc.RPL_WHOISSERVER,
1090 Params: []string{dc.nick, nick, server, serverInfo},
1091 })
1092 })
1093 case irc.RPL_WHOISOPERATOR:
1094 var nick string
1095 if err := parseMessageParams(msg, nil, &nick); err != nil {
1096 return err
1097 }
1098
1099 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1100 nick := dc.marshalEntity(uc.network, nick)
1101 dc.SendMessage(&irc.Message{
1102 Prefix: dc.srv.prefix(),
1103 Command: irc.RPL_WHOISOPERATOR,
1104 Params: []string{dc.nick, nick, "is an IRC operator"},
1105 })
1106 })
1107 case irc.RPL_WHOISIDLE:
1108 var nick string
1109 if err := parseMessageParams(msg, nil, &nick, nil); err != nil {
1110 return err
1111 }
1112
1113 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1114 nick := dc.marshalEntity(uc.network, nick)
1115 params := []string{dc.nick, nick}
1116 params = append(params, msg.Params[2:]...)
1117 dc.SendMessage(&irc.Message{
1118 Prefix: dc.srv.prefix(),
1119 Command: irc.RPL_WHOISIDLE,
1120 Params: params,
1121 })
1122 })
1123 case irc.RPL_WHOISCHANNELS:
1124 var nick, channelList string
1125 if err := parseMessageParams(msg, nil, &nick, &channelList); err != nil {
1126 return err
1127 }
1128 channels := splitSpace(channelList)
1129
1130 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1131 nick := dc.marshalEntity(uc.network, nick)
1132 channelList := make([]string, len(channels))
1133 for i, channel := range channels {
1134 prefix, channel := uc.parseMembershipPrefix(channel)
1135 channel = dc.marshalEntity(uc.network, channel)
1136 channelList[i] = prefix.Format(dc) + channel
1137 }
1138 channels := strings.Join(channelList, " ")
1139 dc.SendMessage(&irc.Message{
1140 Prefix: dc.srv.prefix(),
1141 Command: irc.RPL_WHOISCHANNELS,
1142 Params: []string{dc.nick, nick, channels},
1143 })
1144 })
1145 case irc.RPL_ENDOFWHOIS:
1146 var nick string
1147 if err := parseMessageParams(msg, nil, &nick); err != nil {
1148 return err
1149 }
1150
1151 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1152 nick := dc.marshalEntity(uc.network, nick)
1153 dc.SendMessage(&irc.Message{
1154 Prefix: dc.srv.prefix(),
1155 Command: irc.RPL_ENDOFWHOIS,
1156 Params: []string{dc.nick, nick, "End of /WHOIS list"},
1157 })
1158 })
1159 case "INVITE":
1160 var nick, channel string
1161 if err := parseMessageParams(msg, &nick, &channel); err != nil {
1162 return err
1163 }
1164
1165 uc.forEachDownstream(func(dc *downstreamConn) {
1166 dc.SendMessage(&irc.Message{
1167 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
1168 Command: "INVITE",
1169 Params: []string{dc.marshalEntity(uc.network, nick), dc.marshalEntity(uc.network, channel)},
1170 })
1171 })
1172 case irc.RPL_INVITING:
1173 var nick, channel string
1174 if err := parseMessageParams(msg, &nick, &channel); err != nil {
1175 return err
1176 }
1177
1178 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1179 dc.SendMessage(&irc.Message{
1180 Prefix: dc.srv.prefix(),
1181 Command: irc.RPL_INVITING,
1182 Params: []string{dc.nick, dc.marshalEntity(uc.network, nick), dc.marshalEntity(uc.network, channel)},
1183 })
1184 })
1185 case irc.ERR_UNKNOWNCOMMAND, irc.RPL_TRYAGAIN:
1186 var command, reason string
1187 if err := parseMessageParams(msg, nil, &command, &reason); err != nil {
1188 return err
1189 }
1190
1191 if command == "LIST" {
1192 ok := uc.endPendingLISTs(false)
1193 if !ok {
1194 return fmt.Errorf("unexpected response for LIST: %q: no matching pending LIST", msg.Command)
1195 }
1196 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1197 dc.SendMessage(&irc.Message{
1198 Prefix: uc.srv.prefix(),
1199 Command: msg.Command,
1200 Params: []string{dc.nick, "LIST", reason},
1201 })
1202 })
1203 }
1204 case irc.RPL_AWAY:
1205 var nick, reason string
1206 if err := parseMessageParams(msg, nil, &nick, &reason); err != nil {
1207 return err
1208 }
1209
1210 uc.forEachDownstream(func(dc *downstreamConn) {
1211 dc.SendMessage(&irc.Message{
1212 Prefix: dc.srv.prefix(),
1213 Command: irc.RPL_AWAY,
1214 Params: []string{dc.nick, dc.marshalEntity(uc.network, nick), reason},
1215 })
1216 })
1217 case "AWAY":
1218 if msg.Prefix == nil {
1219 return fmt.Errorf("expected a prefix")
1220 }
1221
1222 uc.forEachDownstream(func(dc *downstreamConn) {
1223 if !dc.caps["away-notify"] {
1224 return
1225 }
1226 dc.SendMessage(&irc.Message{
1227 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
1228 Command: "AWAY",
1229 Params: msg.Params,
1230 })
1231 })
1232 case irc.RPL_BANLIST, irc.RPL_INVITELIST, irc.RPL_EXCEPTLIST:
1233 var channel, mask string
1234 if err := parseMessageParams(msg, nil, &channel, &mask); err != nil {
1235 return err
1236 }
1237 var addNick, addTime string
1238 if len(msg.Params) >= 5 {
1239 addNick = msg.Params[3]
1240 addTime = msg.Params[4]
1241 }
1242
1243 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1244 channel := dc.marshalEntity(uc.network, channel)
1245
1246 var params []string
1247 if addNick != "" && addTime != "" {
1248 addNick := dc.marshalEntity(uc.network, addNick)
1249 params = []string{dc.nick, channel, mask, addNick, addTime}
1250 } else {
1251 params = []string{dc.nick, channel, mask}
1252 }
1253
1254 dc.SendMessage(&irc.Message{
1255 Prefix: dc.srv.prefix(),
1256 Command: msg.Command,
1257 Params: params,
1258 })
1259 })
1260 case irc.RPL_ENDOFBANLIST, irc.RPL_ENDOFINVITELIST, irc.RPL_ENDOFEXCEPTLIST:
1261 var channel, trailing string
1262 if err := parseMessageParams(msg, nil, &channel, &trailing); err != nil {
1263 return err
1264 }
1265
1266 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1267 upstreamChannel := dc.marshalEntity(uc.network, channel)
1268 dc.SendMessage(&irc.Message{
1269 Prefix: dc.srv.prefix(),
1270 Command: msg.Command,
1271 Params: []string{dc.nick, upstreamChannel, trailing},
1272 })
1273 })
1274 case "TAGMSG":
1275 // TODO: relay to downstream connections that accept message-tags
1276 case "ACK":
1277 // Ignore
1278 case irc.RPL_NOWAWAY, irc.RPL_UNAWAY:
1279 // Ignore
1280 case irc.RPL_YOURHOST, irc.RPL_CREATED:
1281 // Ignore
1282 case irc.RPL_LUSERCLIENT, irc.RPL_LUSEROP, irc.RPL_LUSERUNKNOWN, irc.RPL_LUSERCHANNELS, irc.RPL_LUSERME:
1283 // Ignore
1284 case irc.RPL_MOTDSTART, irc.RPL_MOTD, irc.RPL_ENDOFMOTD:
1285 // Ignore
1286 case irc.RPL_LISTSTART:
1287 // Ignore
1288 case rpl_localusers, rpl_globalusers:
1289 // Ignore
1290 case irc.RPL_STATSVLINE, rpl_statsping, irc.RPL_STATSBLINE, irc.RPL_STATSDLINE:
1291 // Ignore
1292 default:
1293 uc.logger.Printf("unhandled message: %v", msg)
1294 }
1295 return nil
1296}
1297
1298func (uc *upstreamConn) handleSupportedCaps(capsStr string) {
1299 caps := strings.Fields(capsStr)
1300 for _, s := range caps {
1301 kv := strings.SplitN(s, "=", 2)
1302 k := strings.ToLower(kv[0])
1303 var v string
1304 if len(kv) == 2 {
1305 v = kv[1]
1306 }
1307 uc.supportedCaps[k] = v
1308 }
1309}
1310
1311func (uc *upstreamConn) requestCaps() {
1312 var requestCaps []string
1313 for c := range permanentUpstreamCaps {
1314 if _, ok := uc.supportedCaps[c]; ok && !uc.caps[c] {
1315 requestCaps = append(requestCaps, c)
1316 }
1317 }
1318
1319 if uc.requestSASL() && !uc.caps["sasl"] {
1320 requestCaps = append(requestCaps, "sasl")
1321 }
1322
1323 if len(requestCaps) == 0 {
1324 return
1325 }
1326
1327 uc.SendMessage(&irc.Message{
1328 Command: "CAP",
1329 Params: []string{"REQ", strings.Join(requestCaps, " ")},
1330 })
1331}
1332
1333func (uc *upstreamConn) requestSASL() bool {
1334 if uc.network.SASL.Mechanism == "" {
1335 return false
1336 }
1337
1338 v, ok := uc.supportedCaps["sasl"]
1339 if !ok {
1340 return false
1341 }
1342 if v != "" {
1343 mechanisms := strings.Split(v, ",")
1344 found := false
1345 for _, mech := range mechanisms {
1346 if strings.EqualFold(mech, uc.network.SASL.Mechanism) {
1347 found = true
1348 break
1349 }
1350 }
1351 if !found {
1352 return false
1353 }
1354 }
1355
1356 return true
1357}
1358
1359func (uc *upstreamConn) handleCapAck(name string, ok bool) error {
1360 uc.caps[name] = ok
1361
1362 switch name {
1363 case "sasl":
1364 if !ok {
1365 uc.logger.Printf("server refused to acknowledge the SASL capability")
1366 return nil
1367 }
1368
1369 auth := &uc.network.SASL
1370 switch auth.Mechanism {
1371 case "PLAIN":
1372 uc.logger.Printf("starting SASL PLAIN authentication with username %q", auth.Plain.Username)
1373 uc.saslClient = sasl.NewPlainClient("", auth.Plain.Username, auth.Plain.Password)
1374 default:
1375 return fmt.Errorf("unsupported SASL mechanism %q", name)
1376 }
1377
1378 uc.SendMessage(&irc.Message{
1379 Command: "AUTHENTICATE",
1380 Params: []string{auth.Mechanism},
1381 })
1382 default:
1383 if permanentUpstreamCaps[name] {
1384 break
1385 }
1386 uc.logger.Printf("received CAP ACK/NAK for a cap we don't support: %v", name)
1387 }
1388 return nil
1389}
1390
1391func splitSpace(s string) []string {
1392 return strings.FieldsFunc(s, func(r rune) bool {
1393 return r == ' '
1394 })
1395}
1396
1397func (uc *upstreamConn) register() {
1398 uc.nick = uc.network.Nick
1399 uc.username = uc.network.Username
1400 if uc.username == "" {
1401 uc.username = uc.nick
1402 }
1403 uc.realname = uc.network.Realname
1404 if uc.realname == "" {
1405 uc.realname = uc.nick
1406 }
1407
1408 uc.SendMessage(&irc.Message{
1409 Command: "CAP",
1410 Params: []string{"LS", "302"},
1411 })
1412
1413 if uc.network.Pass != "" {
1414 uc.SendMessage(&irc.Message{
1415 Command: "PASS",
1416 Params: []string{uc.network.Pass},
1417 })
1418 }
1419
1420 uc.SendMessage(&irc.Message{
1421 Command: "NICK",
1422 Params: []string{uc.nick},
1423 })
1424 uc.SendMessage(&irc.Message{
1425 Command: "USER",
1426 Params: []string{uc.username, "0", "*", uc.realname},
1427 })
1428}
1429
1430func (uc *upstreamConn) runUntilRegistered() error {
1431 for !uc.registered {
1432 msg, err := uc.ReadMessage()
1433 if err != nil {
1434 return fmt.Errorf("failed to read message: %v", err)
1435 }
1436
1437 if err := uc.handleMessage(msg); err != nil {
1438 return fmt.Errorf("failed to handle message %q: %v", msg, err)
1439 }
1440 }
1441
1442 for _, command := range uc.network.ConnectCommands {
1443 m, err := irc.ParseMessage(command)
1444 if err != nil {
1445 uc.logger.Printf("failed to parse connect command %q: %v", command, err)
1446 } else {
1447 uc.SendMessage(m)
1448 }
1449 }
1450
1451 return nil
1452}
1453
1454func (uc *upstreamConn) readMessages(ch chan<- event) error {
1455 for {
1456 msg, err := uc.ReadMessage()
1457 if err == io.EOF {
1458 break
1459 } else if err != nil {
1460 return fmt.Errorf("failed to read IRC command: %v", err)
1461 }
1462
1463 ch <- eventUpstreamMessage{msg, uc}
1464 }
1465
1466 return nil
1467}
1468
1469func (uc *upstreamConn) SendMessageLabeled(downstreamID uint64, msg *irc.Message) {
1470 if uc.caps["labeled-response"] {
1471 if msg.Tags == nil {
1472 msg.Tags = make(map[string]irc.TagValue)
1473 }
1474 msg.Tags["label"] = irc.TagValue(fmt.Sprintf("sd-%d-%d", downstreamID, uc.nextLabelID))
1475 uc.nextLabelID++
1476 }
1477 uc.SendMessage(msg)
1478}
1479
1480// TODO: handle moving logs when a network name changes, when support for this is added
1481func (uc *upstreamConn) appendLog(entity string, msg *irc.Message) {
1482 if uc.srv.LogPath == "" {
1483 return
1484 }
1485
1486 ml, ok := uc.messageLoggers[entity]
1487 if !ok {
1488 ml = newMessageLogger(uc.network, entity)
1489 uc.messageLoggers[entity] = ml
1490 }
1491
1492 if err := ml.Append(msg); err != nil {
1493 uc.logger.Printf("failed to log message: %v", err)
1494 }
1495}
1496
1497// appendHistory appends a message to the history. entity can be empty.
1498func (uc *upstreamConn) appendHistory(entity string, msg *irc.Message) {
1499 detached := false
1500 if ch, ok := uc.network.channels[entity]; ok {
1501 detached = ch.Detached
1502 }
1503
1504 // If no client is offline, no need to append the message to the buffer
1505 if len(uc.network.offlineClients) == 0 && !detached {
1506 return
1507 }
1508
1509 history, ok := uc.network.history[entity]
1510 if !ok {
1511 history = &networkHistory{
1512 offlineClients: make(map[string]uint64),
1513 ring: NewRing(uc.srv.RingCap),
1514 }
1515 uc.network.history[entity] = history
1516
1517 for clientName, _ := range uc.network.offlineClients {
1518 history.offlineClients[clientName] = 0
1519 }
1520
1521 if detached {
1522 // If the channel is detached, online clients act as offline
1523 // clients too
1524 uc.forEachDownstream(func(dc *downstreamConn) {
1525 history.offlineClients[dc.clientName] = 0
1526 })
1527 }
1528 }
1529
1530 history.ring.Produce(msg)
1531}
1532
1533// produce appends a message to the logs, adds it to the history and forwards
1534// it to connected downstream connections.
1535//
1536// If origin is not nil and origin doesn't support echo-message, the message is
1537// forwarded to all connections except origin.
1538func (uc *upstreamConn) produce(target string, msg *irc.Message, origin *downstreamConn) {
1539 if target != "" {
1540 uc.appendLog(target, msg)
1541 }
1542
1543 uc.appendHistory(target, msg)
1544
1545 // Don't forward messages if it's a detached channel
1546 if ch, ok := uc.network.channels[target]; ok && ch.Detached {
1547 return
1548 }
1549
1550 uc.forEachDownstream(func(dc *downstreamConn) {
1551 if dc != origin || dc.caps["echo-message"] {
1552 dc.SendMessage(dc.marshalMessage(msg, uc.network))
1553 }
1554 })
1555}
1556
1557func (uc *upstreamConn) updateAway() {
1558 away := true
1559 uc.forEachDownstream(func(*downstreamConn) {
1560 away = false
1561 })
1562 if away == uc.away {
1563 return
1564 }
1565 if away {
1566 uc.SendMessage(&irc.Message{
1567 Command: "AWAY",
1568 Params: []string{"Auto away"},
1569 })
1570 } else {
1571 uc.SendMessage(&irc.Message{
1572 Command: "AWAY",
1573 })
1574 }
1575 uc.away = away
1576}
Note: See TracBrowser for help on using the repository browser.