source: code/trunk/upstream.go@ 445

Last change on this file since 445 was 443, checked in by hubert, 4 years ago

Don't forward batch tags

We don't want to have the batch tag when calling uc.produce, otherwise
downstream will end up with junk batch ids.

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