source: code/trunk/upstream.go@ 416

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

Don't update history when msgID is empty in appendLog

Log files don't record all message types. If the message isn't inserted
in the log file, don't update the history cursors for downstream
connections.

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