source: code/trunk/upstream.go@ 350

Last change on this file since 350 was 350, checked in by contact, 5 years ago

Sort and split JOIN messages

Sort channels so that channels with a key appear first. Split JOIN
messages so that we don't reach the message size limit.

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