source: code/trunk/upstream.go@ 377

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

Change unix:// to irc+unix://

When Unix socket support will be added for listeners, unix:// will be
ambiguous. It won't be clear whether to setup an IRC server, or some
other kind of server (e.g. identd).

unix:// is still recognized to avoid breaking existing DBs.

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