source: code/trunk/upstream.go@ 439

Last change on this file since 439 was 435, checked in by delthas, 4 years ago

Add customizable auto-detaching, auto-reattaching, relaying.

This uses the fields added previously to the Channel struct to implement
the actual detaching/reattaching/relaying logic.

The FilterDefault values of the messages filters are currently
hardcoded.

The values of the message filters are not currently user-settable.

This introduces a new user event, eventChannelDetach, which stores an
upstreamConn (which might become invalid at the time of processing), and
a channel name, used for auto-detaching. Every time the channel detach
timer is refreshed (by receveing a message, etc.), a new timer is
created on the upstreamChannel, which will dispatch this event after the
duration (and discards the previous timer, if any).

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