source: code/trunk/upstream.go@ 288

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

Improve highlight matching

Detect word boundaries instead of just doing a sub-string check.

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