source: code/trunk/upstream.go@ 398

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

Implement rate limiting for upstream messages

Allow up to 10 outgoing messages in a burst, then throttle to 1 message
each 2 seconds.

Closes: https://todo.sr.ht/~emersion/soju/87

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