source: code/trunk/upstream.go@ 398

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

Implement rate limiting for upstream messages

Allow up to 10 outgoing messages in a burst, then throttle to 1 message
each 2 seconds.

Closes: https://todo.sr.ht/~emersion/soju/87

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