source: code/trunk/upstream.go@ 396

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

Strip message tags in error message

The time tag was causing the registration error messages to be
continuously sent to downstream connections.

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