source: code/trunk/upstream.go@ 351

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

Check upstream address with net.SplitHostPort

Looking for a colon is incorrect, IPv6 addresses can contain colons too.

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