source: code/trunk/upstream.go@ 352

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

Parse upstream URLs with net/url

This allows us to ignore the path part of the URL. This is preliminary
work for unix URLs.

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