source: code/trunk/upstream.go@ 460

Last change on this file since 460 was 460, checked in by contact, 4 years ago

Maintain state for upstream ISUPPORT

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