source: code/trunk/upstream.go@ 424

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

Add message store abstraction

Introduce a messageStore type, which will allow for multiple
implementations (e.g. in the DB or in-memory instead of on-disk).

The message store is per-user so that we don't need to deal with locking
and it's easier to implement per-user limits.

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