source: code/trunk/upstream.go@ 303

Last change on this file since 303 was 303, checked in by delthas, 5 years ago

Add support for TAGMSG and client message tags

Previously we dropped all TAGMSG as well as any client message tag sent
from downstream.

This adds support for properly forwarding TAGMSG and client message tags
from downstreams and upstreams.

TAGMSG messages are intentionally not logged, because they are currently
typically used for +typing, which can generate a lot of traffic and is
only useful for a few seconds after it is sent.

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