source: code/trunk/upstream.go@ 684

Last change on this file since 684 was 684, checked in by contact, 4 years ago

Add support for MONITOR

Add support for MONITOR in single-upstream mode.

Each downstream has its own set of monitored targets. These sets
are merged together to compute the MONITOR commands to send to
upstream.

Each upstream has a set of monitored targets accepted by the server
alongside with their status (online/offline). This is used to
directly send replies to downstreams adding a target another
downstream has already added, and send MONITOR S[TATUS] replies.

Co-authored-by: delthas <delthas@…>

File size: 50.5 KB
RevLine 
[98]1package soju
[13]2
3import (
[652]4 "context"
[307]5 "crypto"
6 "crypto/sha256"
[13]7 "crypto/tls"
[307]8 "crypto/x509"
[95]9 "encoding/base64"
[155]10 "errors"
[13]11 "fmt"
12 "io"
13 "net"
[19]14 "strconv"
[17]15 "strings"
[19]16 "time"
[13]17
[95]18 "github.com/emersion/go-sasl"
[13]19 "gopkg.in/irc.v3"
20)
21
[282]22// permanentUpstreamCaps is the static list of upstream capabilities always
23// requested when supported.
24var permanentUpstreamCaps = map[string]bool{
[559]25 "account-tag": true,
[282]26 "away-notify": true,
27 "batch": true,
[419]28 "extended-join": true,
[448]29 "invite-notify": true,
[282]30 "labeled-response": true,
31 "message-tags": true,
[292]32 "multi-prefix": true,
[282]33 "server-time": true,
[540]34 "setname": true,
[282]35}
36
[399]37type registrationError string
38
39func (err registrationError) Error() string {
40 return fmt.Sprintf("registration error: %v", string(err))
41}
42
[19]43type upstreamChannel struct {
[162]44 Name string
45 conn *upstreamConn
46 Topic string
[405]47 TopicWho *irc.Prefix
[162]48 TopicTime time.Time
49 Status channelStatus
50 modes channelModes
51 creationTime string
[478]52 Members membershipsCasemapMap
[162]53 complete bool
[435]54 detachTimer *time.Timer
[19]55}
56
[435]57func (uc *upstreamChannel) updateAutoDetach(dur time.Duration) {
58 if uc.detachTimer != nil {
59 uc.detachTimer.Stop()
60 uc.detachTimer = nil
61 }
62
63 if dur == 0 {
64 return
65 }
66
67 uc.detachTimer = time.AfterFunc(dur, func() {
68 uc.conn.network.user.events <- eventChannelDetach{
69 uc: uc.conn,
70 name: uc.Name,
71 }
72 })
73}
74
[681]75type pendingUpstreamCommand struct {
76 downstreamID uint64
[682]77 msg *irc.Message
[681]78}
79
[13]80type upstreamConn struct {
[210]81 conn
[16]82
[210]83 network *network
84 user *user
85
[16]86 serverName string
87 availableUserModes string
[139]88 availableChannelModes map[byte]channelModeType
89 availableChannelTypes string
90 availableMemberships []membership
[460]91 isupport map[string]*string
[19]92
[277]93 registered bool
94 nick string
[478]95 nickCM string
[277]96 username string
97 realname string
98 modes userModes
[478]99 channels upstreamChannelCasemapMap
[277]100 supportedCaps map[string]string
[278]101 caps map[string]bool
[277]102 batches map[string]batch
103 away bool
[559]104 account string
[278]105 nextLabelID uint64
[684]106 monitored monitorCasemapMap
[95]107
108 saslClient sasl.Client
109 saslStarted bool
[177]110
[478]111 casemapIsSet bool
112
[682]113 // Queue of commands in progress, indexed by type. The first entry has been
114 // sent to the server and is awaiting reply. The following entries have not
115 // been sent yet.
116 pendingCmds map[string][]pendingUpstreamCommand
[552]117
118 gotMotd bool
[13]119}
120
[77]121func connectToUpstream(network *network) (*upstreamConn, error) {
[502]122 logger := &prefixLogger{network.user.logger, fmt.Sprintf("upstream %q: ", network.GetName())}
[33]123
[352]124 dialer := net.Dialer{Timeout: connectTimeout}
[269]125
[457]126 u, err := network.URL()
[352]127 if err != nil {
[457]128 return nil, err
[352]129 }
[206]130
[269]131 var netConn net.Conn
[352]132 switch u.Scheme {
[269]133 case "ircs":
[352]134 addr := u.Host
[381]135 host, _, err := net.SplitHostPort(u.Host)
136 if err != nil {
137 host = u.Host
138 addr = u.Host + ":6697"
[269]139 }
140
141 logger.Printf("connecting to TLS server at address %q", addr)
[307]142
[455]143 tlsConfig := &tls.Config{ServerName: host, NextProtos: []string{"irc"}}
[307]144 if network.SASL.Mechanism == "EXTERNAL" {
145 if network.SASL.External.CertBlob == nil {
146 return nil, fmt.Errorf("missing certificate for authentication")
147 }
148 if network.SASL.External.PrivKeyBlob == nil {
149 return nil, fmt.Errorf("missing private key for authentication")
150 }
151 key, err := x509.ParsePKCS8PrivateKey(network.SASL.External.PrivKeyBlob)
152 if err != nil {
153 return nil, fmt.Errorf("failed to parse private key: %v", err)
154 }
[381]155 tlsConfig.Certificates = []tls.Certificate{
156 {
157 Certificate: [][]byte{network.SASL.External.CertBlob},
158 PrivateKey: key.(crypto.PrivateKey),
[307]159 },
160 }
161 logger.Printf("using TLS client certificate %x", sha256.Sum256(network.SASL.External.CertBlob))
162 }
163
[381]164 netConn, err = dialer.Dial("tcp", addr)
[352]165 if err != nil {
166 return nil, fmt.Errorf("failed to dial %q: %v", addr, err)
167 }
[381]168
169 // Don't do the TLS handshake immediately, because we need to register
170 // the new connection with identd ASAP. See:
171 // https://todo.sr.ht/~emersion/soju/69#event-41859
172 netConn = tls.Client(netConn, tlsConfig)
[270]173 case "irc+insecure":
[352]174 addr := u.Host
[351]175 if _, _, err := net.SplitHostPort(addr); err != nil {
[270]176 addr = addr + ":6667"
177 }
178
179 logger.Printf("connecting to plain-text server at address %q", addr)
180 netConn, err = dialer.Dial("tcp", addr)
[352]181 if err != nil {
182 return nil, fmt.Errorf("failed to dial %q: %v", addr, err)
183 }
[369]184 case "irc+unix", "unix":
[353]185 logger.Printf("connecting to Unix socket at path %q", u.Path)
186 netConn, err = dialer.Dial("unix", u.Path)
187 if err != nil {
188 return nil, fmt.Errorf("failed to connect to Unix socket %q: %v", u.Path, err)
189 }
[269]190 default:
[352]191 return nil, fmt.Errorf("failed to dial %q: unknown scheme: %v", network.Addr, u.Scheme)
[269]192 }
[33]193
[398]194 options := connOptions{
[402]195 Logger: logger,
[398]196 RateLimitDelay: upstreamMessageDelay,
197 RateLimitBurst: upstreamMessageBurst,
198 }
199
[55]200 uc := &upstreamConn{
[681]201 conn: *newConn(network.user.srv, newNetIRCConn(netConn), &options),
202 network: network,
203 user: network.user,
204 channels: upstreamChannelCasemapMap{newCasemapMap(0)},
205 supportedCaps: make(map[string]string),
206 caps: make(map[string]bool),
207 batches: make(map[string]batch),
208 availableChannelTypes: stdChannelTypes,
209 availableChannelModes: stdChannelModes,
210 availableMemberships: stdMemberships,
211 isupport: make(map[string]*string),
[682]212 pendingCmds: make(map[string][]pendingUpstreamCommand),
[684]213 monitored: monitorCasemapMap{newCasemapMap(0)},
[33]214 }
[55]215 return uc, nil
[33]216}
217
[73]218func (uc *upstreamConn) forEachDownstream(f func(*downstreamConn)) {
[218]219 uc.network.forEachDownstream(f)
[73]220}
221
[161]222func (uc *upstreamConn) forEachDownstreamByID(id uint64, f func(*downstreamConn)) {
[155]223 uc.forEachDownstream(func(dc *downstreamConn) {
224 if id != 0 && id != dc.id {
225 return
226 }
227 f(dc)
228 })
229}
230
[682]231func (uc *upstreamConn) downstreamByID(id uint64) *downstreamConn {
232 for _, dc := range uc.user.downstreamConns {
233 if dc.id == id {
234 return dc
235 }
236 }
237 return nil
238}
239
[55]240func (uc *upstreamConn) getChannel(name string) (*upstreamChannel, error) {
[478]241 ch := uc.channels.Value(name)
242 if ch == nil {
[19]243 return nil, fmt.Errorf("unknown channel %q", name)
244 }
245 return ch, nil
246}
247
[129]248func (uc *upstreamConn) isChannel(entity string) bool {
[454]249 return strings.ContainsRune(uc.availableChannelTypes, rune(entity[0]))
[129]250}
251
[478]252func (uc *upstreamConn) isOurNick(nick string) bool {
253 return uc.nickCM == uc.network.casemap(nick)
254}
255
[682]256func (uc *upstreamConn) endPendingCommands() {
257 for _, l := range uc.pendingCmds {
258 for _, pendingCmd := range l {
259 dc := uc.downstreamByID(pendingCmd.downstreamID)
260 if dc == nil {
261 continue
262 }
263
264 switch pendingCmd.msg.Command {
265 case "LIST":
266 dc.SendMessage(&irc.Message{
267 Prefix: dc.srv.prefix(),
268 Command: irc.RPL_LISTEND,
269 Params: []string{dc.nick, "End of /LIST"},
270 })
271 case "WHO":
272 mask := "*"
273 if len(pendingCmd.msg.Params) > 0 {
274 mask = pendingCmd.msg.Params[0]
275 }
276 dc.SendMessage(&irc.Message{
277 Prefix: dc.srv.prefix(),
278 Command: irc.RPL_ENDOFWHO,
279 Params: []string{dc.nick, mask, "End of /WHO"},
280 })
281 default:
282 panic(fmt.Errorf("Unsupported pending command %q", pendingCmd.msg.Command))
283 }
284 }
[177]285 }
[682]286
287 uc.pendingCmds = make(map[string][]pendingUpstreamCommand)
[177]288}
289
[682]290func (uc *upstreamConn) sendNextPendingCommand(cmd string) {
291 if len(uc.pendingCmds[cmd]) == 0 {
[681]292 return
[177]293 }
[682]294 uc.SendMessage(uc.pendingCmds[cmd][0].msg)
[177]295}
296
[682]297func (uc *upstreamConn) enqueueCommand(dc *downstreamConn, msg *irc.Message) {
298 switch msg.Command {
299 case "LIST", "WHO":
300 // Supported
301 default:
302 panic(fmt.Errorf("Unsupported pending command %q", msg.Command))
303 }
304
305 uc.pendingCmds[msg.Command] = append(uc.pendingCmds[msg.Command], pendingUpstreamCommand{
[681]306 downstreamID: dc.id,
[682]307 msg: msg,
[681]308 })
309
[682]310 if len(uc.pendingCmds[msg.Command]) == 1 {
311 uc.sendNextPendingCommand(msg.Command)
[177]312 }
[681]313}
[177]314
[682]315func (uc *upstreamConn) currentPendingCommand(cmd string) (*downstreamConn, *irc.Message) {
316 if len(uc.pendingCmds[cmd]) == 0 {
[681]317 return nil, nil
318 }
319
[682]320 pendingCmd := uc.pendingCmds[cmd][0]
321 return uc.downstreamByID(pendingCmd.downstreamID), pendingCmd.msg
[681]322}
[177]323
[682]324func (uc *upstreamConn) dequeueCommand(cmd string) (*downstreamConn, *irc.Message) {
325 dc, msg := uc.currentPendingCommand(cmd)
[681]326
[682]327 if len(uc.pendingCmds[cmd]) > 0 {
328 copy(uc.pendingCmds[cmd], uc.pendingCmds[cmd][1:])
329 uc.pendingCmds[cmd] = uc.pendingCmds[cmd][:len(uc.pendingCmds[cmd])-1]
[177]330 }
[681]331
[682]332 uc.sendNextPendingCommand(cmd)
[681]333
[682]334 return dc, msg
[177]335}
336
[292]337func (uc *upstreamConn) parseMembershipPrefix(s string) (ms *memberships, nick string) {
338 memberships := make(memberships, 0, 4)
339 i := 0
[139]340 for _, m := range uc.availableMemberships {
[292]341 if i >= len(s) {
342 break
[139]343 }
[292]344 if s[i] == m.Prefix {
345 memberships = append(memberships, m)
346 i++
347 }
[139]348 }
[292]349 return &memberships, s[i:]
[139]350}
351
[55]352func (uc *upstreamConn) handleMessage(msg *irc.Message) error {
[155]353 var label string
354 if l, ok := msg.GetTag("label"); ok {
355 label = l
[526]356 delete(msg.Tags, "label")
[155]357 }
358
[153]359 var msgBatch *batch
360 if batchName, ok := msg.GetTag("batch"); ok {
361 b, ok := uc.batches[batchName]
362 if !ok {
363 return fmt.Errorf("unexpected batch reference: batch was not defined: %q", batchName)
364 }
365 msgBatch = &b
[155]366 if label == "" {
367 label = msgBatch.Label
368 }
[443]369 delete(msg.Tags, "batch")
[153]370 }
371
[161]372 var downstreamID uint64 = 0
[155]373 if label != "" {
374 var labelOffset uint64
[161]375 n, err := fmt.Sscanf(label, "sd-%d-%d", &downstreamID, &labelOffset)
[155]376 if err == nil && n < 2 {
377 err = errors.New("not enough arguments")
378 }
379 if err != nil {
380 return fmt.Errorf("unexpected message label: invalid downstream reference for label %q: %v", label, err)
381 }
382 }
383
[216]384 if _, ok := msg.Tags["time"]; !ok {
[240]385 msg.Tags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
[216]386 }
387
[13]388 switch msg.Command {
389 case "PING":
[60]390 uc.SendMessage(&irc.Message{
[13]391 Command: "PONG",
[68]392 Params: msg.Params,
[60]393 })
[33]394 return nil
[303]395 case "NOTICE", "PRIVMSG", "TAGMSG":
[273]396 if msg.Prefix == nil {
397 return fmt.Errorf("expected a prefix")
398 }
399
[286]400 var entity, text string
[303]401 if msg.Command != "TAGMSG" {
402 if err := parseMessageParams(msg, &entity, &text); err != nil {
403 return err
404 }
405 } else {
406 if err := parseMessageParams(msg, &entity); err != nil {
407 return err
408 }
[286]409 }
410
411 if msg.Prefix.Name == serviceNick {
412 uc.logger.Printf("skipping %v from soju's service: %v", msg.Command, msg)
413 break
414 }
415 if entity == serviceNick {
416 uc.logger.Printf("skipping %v to soju's service: %v", msg.Command, msg)
417 break
418 }
419
[171]420 if msg.Prefix.User == "" && msg.Prefix.Host == "" { // server message
[239]421 uc.produce("", msg, nil)
[303]422 } else { // regular user message
[217]423 target := entity
[478]424 if uc.isOurNick(target) {
[178]425 target = msg.Prefix.Name
426 }
[287]427
[478]428 ch := uc.network.channels.Value(target)
[505]429 if ch != nil && msg.Command != "TAGMSG" {
[435]430 if ch.Detached {
[499]431 uc.handleDetachedMessage(ch, msg)
[435]432 }
433
[499]434 highlight := uc.network.isHighlight(msg)
[435]435 if ch.DetachOn == FilterMessage || ch.DetachOn == FilterDefault || (ch.DetachOn == FilterHighlight && highlight) {
436 uc.updateChannelAutoDetach(target)
437 }
[287]438 }
[435]439
440 uc.produce(target, msg, nil)
[171]441 }
[92]442 case "CAP":
[95]443 var subCmd string
444 if err := parseMessageParams(msg, nil, &subCmd); err != nil {
445 return err
[92]446 }
[95]447 subCmd = strings.ToUpper(subCmd)
448 subParams := msg.Params[2:]
449 switch subCmd {
450 case "LS":
451 if len(subParams) < 1 {
452 return newNeedMoreParamsError(msg.Command)
453 }
[281]454 caps := subParams[len(subParams)-1]
[95]455 more := len(subParams) >= 2 && msg.Params[len(subParams)-2] == "*"
[92]456
[281]457 uc.handleSupportedCaps(caps)
[92]458
[95]459 if more {
460 break // wait to receive all capabilities
461 }
462
[281]463 uc.requestCaps()
[152]464
[95]465 if uc.requestSASL() {
466 break // we'll send CAP END after authentication is completed
467 }
468
[92]469 uc.SendMessage(&irc.Message{
470 Command: "CAP",
471 Params: []string{"END"},
472 })
[95]473 case "ACK", "NAK":
474 if len(subParams) < 1 {
475 return newNeedMoreParamsError(msg.Command)
476 }
477 caps := strings.Fields(subParams[0])
478
479 for _, name := range caps {
480 if err := uc.handleCapAck(strings.ToLower(name), subCmd == "ACK"); err != nil {
481 return err
482 }
483 }
484
[281]485 if uc.registered {
486 uc.forEachDownstream(func(dc *downstreamConn) {
487 dc.updateSupportedCaps()
488 })
489 }
490 case "NEW":
491 if len(subParams) < 1 {
492 return newNeedMoreParamsError(msg.Command)
493 }
494 uc.handleSupportedCaps(subParams[0])
495 uc.requestCaps()
496 case "DEL":
497 if len(subParams) < 1 {
498 return newNeedMoreParamsError(msg.Command)
499 }
500 caps := strings.Fields(subParams[0])
501
502 for _, c := range caps {
503 delete(uc.supportedCaps, c)
504 delete(uc.caps, c)
505 }
506
507 if uc.registered {
508 uc.forEachDownstream(func(dc *downstreamConn) {
509 dc.updateSupportedCaps()
510 })
511 }
[95]512 default:
513 uc.logger.Printf("unhandled message: %v", msg)
[92]514 }
[95]515 case "AUTHENTICATE":
516 if uc.saslClient == nil {
517 return fmt.Errorf("received unexpected AUTHENTICATE message")
518 }
519
520 // TODO: if a challenge is 400 bytes long, buffer it
521 var challengeStr string
522 if err := parseMessageParams(msg, &challengeStr); err != nil {
523 uc.SendMessage(&irc.Message{
524 Command: "AUTHENTICATE",
525 Params: []string{"*"},
526 })
527 return err
528 }
529
530 var challenge []byte
531 if challengeStr != "+" {
532 var err error
533 challenge, err = base64.StdEncoding.DecodeString(challengeStr)
534 if err != nil {
535 uc.SendMessage(&irc.Message{
536 Command: "AUTHENTICATE",
537 Params: []string{"*"},
538 })
539 return err
540 }
541 }
542
543 var resp []byte
544 var err error
545 if !uc.saslStarted {
546 _, resp, err = uc.saslClient.Start()
547 uc.saslStarted = true
548 } else {
549 resp, err = uc.saslClient.Next(challenge)
550 }
551 if err != nil {
552 uc.SendMessage(&irc.Message{
553 Command: "AUTHENTICATE",
554 Params: []string{"*"},
555 })
556 return err
557 }
558
559 // TODO: send response in multiple chunks if >= 400 bytes
560 var respStr = "+"
[318]561 if len(resp) != 0 {
[95]562 respStr = base64.StdEncoding.EncodeToString(resp)
563 }
564
565 uc.SendMessage(&irc.Message{
566 Command: "AUTHENTICATE",
567 Params: []string{respStr},
568 })
[125]569 case irc.RPL_LOGGEDIN:
[559]570 if err := parseMessageParams(msg, nil, nil, &uc.account); err != nil {
[95]571 return err
572 }
[559]573 uc.logger.Printf("logged in with account %q", uc.account)
[125]574 case irc.RPL_LOGGEDOUT:
[559]575 uc.account = ""
[95]576 uc.logger.Printf("logged out")
[125]577 case irc.ERR_NICKLOCKED, irc.RPL_SASLSUCCESS, irc.ERR_SASLFAIL, irc.ERR_SASLTOOLONG, irc.ERR_SASLABORTED:
[95]578 var info string
579 if err := parseMessageParams(msg, nil, &info); err != nil {
580 return err
581 }
582 switch msg.Command {
[125]583 case irc.ERR_NICKLOCKED:
[95]584 uc.logger.Printf("invalid nick used with SASL authentication: %v", info)
[125]585 case irc.ERR_SASLFAIL:
[95]586 uc.logger.Printf("SASL authentication failed: %v", info)
[125]587 case irc.ERR_SASLTOOLONG:
[95]588 uc.logger.Printf("SASL message too long: %v", info)
589 }
590
591 uc.saslClient = nil
592 uc.saslStarted = false
593
594 uc.SendMessage(&irc.Message{
595 Command: "CAP",
596 Params: []string{"END"},
597 })
[14]598 case irc.RPL_WELCOME:
[55]599 uc.registered = true
600 uc.logger.Printf("connection registered")
[19]601
[478]602 if uc.network.channels.Len() > 0 {
[350]603 var channels, keys []string
[478]604 for _, entry := range uc.network.channels.innerMap {
605 ch := entry.value.(*Channel)
[350]606 channels = append(channels, ch.Name)
[310]607 keys = append(keys, ch.Key)
608 }
[350]609
610 for _, msg := range join(channels, keys) {
611 uc.SendMessage(msg)
612 }
[19]613 }
[16]614 case irc.RPL_MYINFO:
[139]615 if err := parseMessageParams(msg, nil, &uc.serverName, nil, &uc.availableUserModes, nil); err != nil {
[43]616 return err
[16]617 }
[139]618 case irc.RPL_ISUPPORT:
619 if err := parseMessageParams(msg, nil, nil); err != nil {
620 return err
[16]621 }
[463]622
623 var downstreamIsupport []string
[139]624 for _, token := range msg.Params[1 : len(msg.Params)-1] {
625 parameter := token
[460]626 var negate, hasValue bool
627 var value string
[139]628 if strings.HasPrefix(token, "-") {
629 negate = true
630 token = token[1:]
[459]631 } else if i := strings.IndexByte(token, '='); i >= 0 {
632 parameter = token[:i]
633 value = token[i+1:]
[460]634 hasValue = true
[139]635 }
[460]636
637 if hasValue {
638 uc.isupport[parameter] = &value
639 } else if !negate {
640 uc.isupport[parameter] = nil
641 } else {
642 delete(uc.isupport, parameter)
643 }
644
[462]645 var err error
646 switch parameter {
[478]647 case "CASEMAPPING":
648 casemap, ok := parseCasemappingToken(value)
649 if !ok {
650 casemap = casemapRFC1459
651 }
652 uc.network.updateCasemapping(casemap)
653 uc.nickCM = uc.network.casemap(uc.nick)
654 uc.casemapIsSet = true
[462]655 case "CHANMODES":
656 if !negate {
657 err = uc.handleChanModes(value)
658 } else {
659 uc.availableChannelModes = stdChannelModes
660 }
661 case "CHANTYPES":
662 if !negate {
[139]663 uc.availableChannelTypes = value
[462]664 } else {
665 uc.availableChannelTypes = stdChannelTypes
[139]666 }
[462]667 case "PREFIX":
668 if !negate {
669 err = uc.handleMemberships(value)
670 } else {
671 uc.availableMemberships = stdMemberships
672 }
[139]673 }
[462]674 if err != nil {
675 return err
676 }
[463]677
678 if passthroughIsupport[parameter] {
679 downstreamIsupport = append(downstreamIsupport, token)
680 }
[139]681 }
[463]682
683 uc.forEachDownstream(func(dc *downstreamConn) {
684 if dc.network == nil {
685 return
686 }
687 msgs := generateIsupport(dc.srv.prefix(), dc.nick, downstreamIsupport)
688 for _, msg := range msgs {
689 dc.SendMessage(msg)
690 }
691 })
[478]692 case irc.ERR_NOMOTD, irc.RPL_ENDOFMOTD:
693 if !uc.casemapIsSet {
694 // upstream did not send any CASEMAPPING token, thus
695 // we assume it implements the old RFCs with rfc1459.
696 uc.casemapIsSet = true
697 uc.network.updateCasemapping(casemapRFC1459)
698 uc.nickCM = uc.network.casemap(uc.nick)
699 }
[552]700
701 if !uc.gotMotd {
702 // Ignore the initial MOTD upon connection, but forward
703 // subsequent MOTD messages downstream
704 uc.gotMotd = true
705 return nil
706 }
707
[553]708 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
709 dc.SendMessage(&irc.Message{
710 Prefix: uc.srv.prefix(),
[552]711 Command: msg.Command,
[553]712 Params: msg.Params,
[552]713 })
714 })
[153]715 case "BATCH":
716 var tag string
717 if err := parseMessageParams(msg, &tag); err != nil {
718 return err
719 }
720
721 if strings.HasPrefix(tag, "+") {
722 tag = tag[1:]
723 if _, ok := uc.batches[tag]; ok {
724 return fmt.Errorf("unexpected BATCH reference tag: batch was already defined: %q", tag)
725 }
726 var batchType string
727 if err := parseMessageParams(msg, nil, &batchType); err != nil {
728 return err
729 }
[155]730 label := label
731 if label == "" && msgBatch != nil {
732 label = msgBatch.Label
733 }
[153]734 uc.batches[tag] = batch{
735 Type: batchType,
736 Params: msg.Params[2:],
737 Outer: msgBatch,
[155]738 Label: label,
[153]739 }
740 } else if strings.HasPrefix(tag, "-") {
741 tag = tag[1:]
742 if _, ok := uc.batches[tag]; !ok {
743 return fmt.Errorf("unknown BATCH reference tag: %q", tag)
744 }
745 delete(uc.batches, tag)
746 } else {
747 return fmt.Errorf("unexpected BATCH reference tag: missing +/- prefix: %q", tag)
748 }
[42]749 case "NICK":
[83]750 if msg.Prefix == nil {
751 return fmt.Errorf("expected a prefix")
752 }
753
[43]754 var newNick string
755 if err := parseMessageParams(msg, &newNick); err != nil {
756 return err
[42]757 }
758
[244]759 me := false
[478]760 if uc.isOurNick(msg.Prefix.Name) {
[55]761 uc.logger.Printf("changed nick from %q to %q", uc.nick, newNick)
[244]762 me = true
[55]763 uc.nick = newNick
[478]764 uc.nickCM = uc.network.casemap(uc.nick)
[42]765 }
766
[478]767 for _, entry := range uc.channels.innerMap {
768 ch := entry.value.(*upstreamChannel)
769 memberships := ch.Members.Value(msg.Prefix.Name)
770 if memberships != nil {
771 ch.Members.Delete(msg.Prefix.Name)
772 ch.Members.SetValue(newNick, memberships)
[215]773 uc.appendLog(ch.Name, msg)
[42]774 }
775 }
[82]776
[244]777 if !me {
[82]778 uc.forEachDownstream(func(dc *downstreamConn) {
[261]779 dc.SendMessage(dc.marshalMessage(msg, uc.network))
[82]780 })
[296]781 } else {
782 uc.forEachDownstream(func(dc *downstreamConn) {
783 dc.updateNick()
784 })
[82]785 }
[540]786 case "SETNAME":
787 if msg.Prefix == nil {
788 return fmt.Errorf("expected a prefix")
789 }
790
791 var newRealname string
792 if err := parseMessageParams(msg, &newRealname); err != nil {
793 return err
794 }
795
796 // TODO: consider appending this message to logs
797
798 if uc.isOurNick(msg.Prefix.Name) {
799 uc.logger.Printf("changed realname from %q to %q", uc.realname, newRealname)
800 uc.realname = newRealname
801
802 uc.forEachDownstream(func(dc *downstreamConn) {
803 dc.updateRealname()
804 })
805 } else {
806 uc.forEachDownstream(func(dc *downstreamConn) {
807 dc.SendMessage(dc.marshalMessage(msg, uc.network))
808 })
809 }
[69]810 case "JOIN":
811 if msg.Prefix == nil {
812 return fmt.Errorf("expected a prefix")
813 }
[42]814
[43]815 var channels string
816 if err := parseMessageParams(msg, &channels); err != nil {
817 return err
[19]818 }
[34]819
[43]820 for _, ch := range strings.Split(channels, ",") {
[478]821 if uc.isOurNick(msg.Prefix.Name) {
[55]822 uc.logger.Printf("joined channel %q", ch)
[478]823 members := membershipsCasemapMap{newCasemapMap(0)}
824 members.casemap = uc.network.casemap
825 uc.channels.SetValue(ch, &upstreamChannel{
[34]826 Name: ch,
[55]827 conn: uc,
[478]828 Members: members,
829 })
[435]830 uc.updateChannelAutoDetach(ch)
[139]831
832 uc.SendMessage(&irc.Message{
833 Command: "MODE",
834 Params: []string{ch},
835 })
[34]836 } else {
[55]837 ch, err := uc.getChannel(ch)
[34]838 if err != nil {
839 return err
840 }
[478]841 ch.Members.SetValue(msg.Prefix.Name, &memberships{})
[19]842 }
[69]843
[245]844 chMsg := msg.Copy()
845 chMsg.Params[0] = ch
846 uc.produce(ch, chMsg, nil)
[19]847 }
[69]848 case "PART":
849 if msg.Prefix == nil {
850 return fmt.Errorf("expected a prefix")
851 }
[34]852
[43]853 var channels string
854 if err := parseMessageParams(msg, &channels); err != nil {
855 return err
[34]856 }
857
[43]858 for _, ch := range strings.Split(channels, ",") {
[478]859 if uc.isOurNick(msg.Prefix.Name) {
[55]860 uc.logger.Printf("parted channel %q", ch)
[478]861 uch := uc.channels.Value(ch)
862 if uch != nil {
863 uc.channels.Delete(ch)
[435]864 uch.updateAutoDetach(0)
865 }
[34]866 } else {
[55]867 ch, err := uc.getChannel(ch)
[34]868 if err != nil {
869 return err
870 }
[478]871 ch.Members.Delete(msg.Prefix.Name)
[34]872 }
[69]873
[245]874 chMsg := msg.Copy()
875 chMsg.Params[0] = ch
876 uc.produce(ch, chMsg, nil)
[34]877 }
[159]878 case "KICK":
879 if msg.Prefix == nil {
880 return fmt.Errorf("expected a prefix")
881 }
882
883 var channel, user string
884 if err := parseMessageParams(msg, &channel, &user); err != nil {
885 return err
886 }
887
[478]888 if uc.isOurNick(user) {
[159]889 uc.logger.Printf("kicked from channel %q by %s", channel, msg.Prefix.Name)
[478]890 uc.channels.Delete(channel)
[159]891 } else {
892 ch, err := uc.getChannel(channel)
893 if err != nil {
894 return err
895 }
[478]896 ch.Members.Delete(user)
[159]897 }
898
[245]899 uc.produce(channel, msg, nil)
[83]900 case "QUIT":
901 if msg.Prefix == nil {
902 return fmt.Errorf("expected a prefix")
903 }
904
[478]905 if uc.isOurNick(msg.Prefix.Name) {
[83]906 uc.logger.Printf("quit")
907 }
908
[478]909 for _, entry := range uc.channels.innerMap {
910 ch := entry.value.(*upstreamChannel)
911 if ch.Members.Has(msg.Prefix.Name) {
912 ch.Members.Delete(msg.Prefix.Name)
[178]913
[215]914 uc.appendLog(ch.Name, msg)
[178]915 }
[83]916 }
917
918 if msg.Prefix.Name != uc.nick {
919 uc.forEachDownstream(func(dc *downstreamConn) {
[261]920 dc.SendMessage(dc.marshalMessage(msg, uc.network))
[83]921 })
922 }
[19]923 case irc.RPL_TOPIC, irc.RPL_NOTOPIC:
[43]924 var name, topic string
925 if err := parseMessageParams(msg, nil, &name, &topic); err != nil {
926 return err
[19]927 }
[55]928 ch, err := uc.getChannel(name)
[19]929 if err != nil {
930 return err
931 }
932 if msg.Command == irc.RPL_TOPIC {
[43]933 ch.Topic = topic
[19]934 } else {
935 ch.Topic = ""
936 }
937 case "TOPIC":
[405]938 if msg.Prefix == nil {
939 return fmt.Errorf("expected a prefix")
940 }
941
[43]942 var name string
[74]943 if err := parseMessageParams(msg, &name); err != nil {
[43]944 return err
[19]945 }
[55]946 ch, err := uc.getChannel(name)
[19]947 if err != nil {
948 return err
949 }
950 if len(msg.Params) > 1 {
951 ch.Topic = msg.Params[1]
[405]952 ch.TopicWho = msg.Prefix.Copy()
953 ch.TopicTime = time.Now() // TODO use msg.Tags["time"]
[19]954 } else {
955 ch.Topic = ""
956 }
[245]957 uc.produce(ch.Name, msg, nil)
[139]958 case "MODE":
959 var name, modeStr string
960 if err := parseMessageParams(msg, &name, &modeStr); err != nil {
961 return err
962 }
963
964 if !uc.isChannel(name) { // user mode change
965 if name != uc.nick {
966 return fmt.Errorf("received MODE message for unknown nick %q", name)
967 }
[553]968
969 if err := uc.modes.Apply(modeStr); err != nil {
970 return err
971 }
972
973 uc.forEachDownstream(func(dc *downstreamConn) {
974 if dc.upstream() == nil {
975 return
976 }
977
978 dc.SendMessage(msg)
979 })
[139]980 } else { // channel mode change
981 ch, err := uc.getChannel(name)
982 if err != nil {
983 return err
984 }
985
[293]986 needMarshaling, err := applyChannelModes(ch, modeStr, msg.Params[2:])
987 if err != nil {
988 return err
[139]989 }
990
[293]991 uc.appendLog(ch.Name, msg)
992
[478]993 c := uc.network.channels.Value(name)
994 if c == nil || !c.Detached {
[338]995 uc.forEachDownstream(func(dc *downstreamConn) {
996 params := make([]string, len(msg.Params))
997 params[0] = dc.marshalEntity(uc.network, name)
998 params[1] = modeStr
999
1000 copy(params[2:], msg.Params[2:])
1001 for i, modeParam := range params[2:] {
1002 if _, ok := needMarshaling[i]; ok {
1003 params[2+i] = dc.marshalEntity(uc.network, modeParam)
1004 }
[293]1005 }
1006
[338]1007 dc.SendMessage(&irc.Message{
1008 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
1009 Command: "MODE",
1010 Params: params,
1011 })
[293]1012 })
[338]1013 }
[139]1014 }
1015 case irc.RPL_UMODEIS:
1016 if err := parseMessageParams(msg, nil); err != nil {
1017 return err
1018 }
1019 modeStr := ""
1020 if len(msg.Params) > 1 {
1021 modeStr = msg.Params[1]
1022 }
1023
1024 uc.modes = ""
1025 if err := uc.modes.Apply(modeStr); err != nil {
1026 return err
1027 }
[553]1028
1029 uc.forEachDownstream(func(dc *downstreamConn) {
1030 if dc.upstream() == nil {
1031 return
1032 }
1033
1034 dc.SendMessage(msg)
1035 })
[139]1036 case irc.RPL_CHANNELMODEIS:
1037 var channel string
1038 if err := parseMessageParams(msg, nil, &channel); err != nil {
1039 return err
1040 }
1041 modeStr := ""
1042 if len(msg.Params) > 2 {
1043 modeStr = msg.Params[2]
1044 }
1045
1046 ch, err := uc.getChannel(channel)
1047 if err != nil {
1048 return err
1049 }
1050
1051 firstMode := ch.modes == nil
1052 ch.modes = make(map[byte]string)
[293]1053 if _, err := applyChannelModes(ch, modeStr, msg.Params[3:]); err != nil {
[139]1054 return err
1055 }
1056 if firstMode {
[478]1057 c := uc.network.channels.Value(channel)
1058 if c == nil || !c.Detached {
[338]1059 modeStr, modeParams := ch.modes.Format()
[139]1060
[338]1061 uc.forEachDownstream(func(dc *downstreamConn) {
1062 params := []string{dc.nick, dc.marshalEntity(uc.network, channel), modeStr}
1063 params = append(params, modeParams...)
[139]1064
[338]1065 dc.SendMessage(&irc.Message{
1066 Prefix: dc.srv.prefix(),
1067 Command: irc.RPL_CHANNELMODEIS,
1068 Params: params,
1069 })
[139]1070 })
[338]1071 }
[139]1072 }
[162]1073 case rpl_creationtime:
1074 var channel, creationTime string
1075 if err := parseMessageParams(msg, nil, &channel, &creationTime); err != nil {
1076 return err
1077 }
1078
1079 ch, err := uc.getChannel(channel)
1080 if err != nil {
1081 return err
1082 }
1083
1084 firstCreationTime := ch.creationTime == ""
1085 ch.creationTime = creationTime
1086 if firstCreationTime {
1087 uc.forEachDownstream(func(dc *downstreamConn) {
1088 dc.SendMessage(&irc.Message{
1089 Prefix: dc.srv.prefix(),
1090 Command: rpl_creationtime,
[403]1091 Params: []string{dc.nick, dc.marshalEntity(uc.network, ch.Name), creationTime},
[162]1092 })
1093 })
1094 }
[19]1095 case rpl_topicwhotime:
[43]1096 var name, who, timeStr string
1097 if err := parseMessageParams(msg, nil, &name, &who, &timeStr); err != nil {
1098 return err
[19]1099 }
[55]1100 ch, err := uc.getChannel(name)
[19]1101 if err != nil {
1102 return err
1103 }
[405]1104 firstTopicWhoTime := ch.TopicWho == nil
1105 ch.TopicWho = irc.ParsePrefix(who)
[43]1106 sec, err := strconv.ParseInt(timeStr, 10, 64)
[19]1107 if err != nil {
1108 return fmt.Errorf("failed to parse topic time: %v", err)
1109 }
1110 ch.TopicTime = time.Unix(sec, 0)
[405]1111 if firstTopicWhoTime {
1112 uc.forEachDownstream(func(dc *downstreamConn) {
1113 topicWho := dc.marshalUserPrefix(uc.network, ch.TopicWho)
1114 dc.SendMessage(&irc.Message{
1115 Prefix: dc.srv.prefix(),
1116 Command: rpl_topicwhotime,
1117 Params: []string{
1118 dc.nick,
1119 dc.marshalEntity(uc.network, ch.Name),
1120 topicWho.String(),
1121 timeStr,
1122 },
1123 })
1124 })
1125 }
[177]1126 case irc.RPL_LIST:
1127 var channel, clients, topic string
1128 if err := parseMessageParams(msg, nil, &channel, &clients, &topic); err != nil {
1129 return err
1130 }
1131
[682]1132 dc, cmd := uc.currentPendingCommand("LIST")
[681]1133 if cmd == nil {
[177]1134 return fmt.Errorf("unexpected RPL_LIST: no matching pending LIST")
[681]1135 } else if dc == nil {
1136 return nil
[177]1137 }
1138
[681]1139 dc.SendMessage(&irc.Message{
1140 Prefix: dc.srv.prefix(),
1141 Command: irc.RPL_LIST,
1142 Params: []string{dc.nick, dc.marshalEntity(uc.network, channel), clients, topic},
[177]1143 })
1144 case irc.RPL_LISTEND:
[682]1145 dc, cmd := uc.dequeueCommand("LIST")
[681]1146 if cmd == nil {
[177]1147 return fmt.Errorf("unexpected RPL_LISTEND: no matching pending LIST")
[681]1148 } else if dc == nil {
1149 return nil
[177]1150 }
[681]1151
1152 dc.SendMessage(&irc.Message{
1153 Prefix: dc.srv.prefix(),
1154 Command: irc.RPL_LISTEND,
1155 Params: []string{dc.nick, "End of /LIST"},
1156 })
[19]1157 case irc.RPL_NAMREPLY:
[43]1158 var name, statusStr, members string
1159 if err := parseMessageParams(msg, nil, &statusStr, &name, &members); err != nil {
1160 return err
[19]1161 }
[140]1162
[478]1163 ch := uc.channels.Value(name)
1164 if ch == nil {
[140]1165 // NAMES on a channel we have not joined, forward to downstream
[161]1166 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1167 channel := dc.marshalEntity(uc.network, name)
[174]1168 members := splitSpace(members)
[140]1169 for i, member := range members {
[292]1170 memberships, nick := uc.parseMembershipPrefix(member)
1171 members[i] = memberships.Format(dc) + dc.marshalEntity(uc.network, nick)
[140]1172 }
1173 memberStr := strings.Join(members, " ")
1174
1175 dc.SendMessage(&irc.Message{
1176 Prefix: dc.srv.prefix(),
1177 Command: irc.RPL_NAMREPLY,
1178 Params: []string{dc.nick, statusStr, channel, memberStr},
1179 })
1180 })
1181 return nil
[19]1182 }
1183
[43]1184 status, err := parseChannelStatus(statusStr)
[19]1185 if err != nil {
1186 return err
1187 }
1188 ch.Status = status
1189
[174]1190 for _, s := range splitSpace(members) {
[292]1191 memberships, nick := uc.parseMembershipPrefix(s)
[478]1192 ch.Members.SetValue(nick, memberships)
[19]1193 }
1194 case irc.RPL_ENDOFNAMES:
[43]1195 var name string
1196 if err := parseMessageParams(msg, nil, &name); err != nil {
1197 return err
[25]1198 }
[140]1199
[478]1200 ch := uc.channels.Value(name)
1201 if ch == nil {
[140]1202 // NAMES on a channel we have not joined, forward to downstream
[161]1203 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1204 channel := dc.marshalEntity(uc.network, name)
[140]1205
1206 dc.SendMessage(&irc.Message{
1207 Prefix: dc.srv.prefix(),
1208 Command: irc.RPL_ENDOFNAMES,
1209 Params: []string{dc.nick, channel, "End of /NAMES list"},
1210 })
1211 })
1212 return nil
[25]1213 }
1214
[34]1215 if ch.complete {
1216 return fmt.Errorf("received unexpected RPL_ENDOFNAMES")
1217 }
[25]1218 ch.complete = true
[27]1219
[478]1220 c := uc.network.channels.Value(name)
1221 if c == nil || !c.Detached {
[338]1222 uc.forEachDownstream(func(dc *downstreamConn) {
1223 forwardChannel(dc, ch)
1224 })
1225 }
[127]1226 case irc.RPL_WHOREPLY:
1227 var channel, username, host, server, nick, mode, trailing string
1228 if err := parseMessageParams(msg, nil, &channel, &username, &host, &server, &nick, &mode, &trailing); err != nil {
1229 return err
1230 }
1231
[682]1232 dc, cmd := uc.currentPendingCommand("WHO")
1233 if cmd == nil {
1234 return fmt.Errorf("unexpected RPL_WHOREPLY: no matching pending WHO")
1235 } else if dc == nil {
1236 return nil
1237 }
1238
[127]1239 parts := strings.SplitN(trailing, " ", 2)
1240 if len(parts) != 2 {
1241 return fmt.Errorf("received malformed RPL_WHOREPLY: wrong trailing parameter: %s", trailing)
1242 }
1243 realname := parts[1]
1244 hops, err := strconv.Atoi(parts[0])
1245 if err != nil {
1246 return fmt.Errorf("received malformed RPL_WHOREPLY: wrong hop count: %s", parts[0])
1247 }
1248 hops++
1249
1250 trailing = strconv.Itoa(hops) + " " + realname
1251
[682]1252 if channel != "*" {
1253 channel = dc.marshalEntity(uc.network, channel)
1254 }
1255 nick = dc.marshalEntity(uc.network, nick)
1256 dc.SendMessage(&irc.Message{
1257 Prefix: dc.srv.prefix(),
1258 Command: irc.RPL_WHOREPLY,
1259 Params: []string{dc.nick, channel, username, host, server, nick, mode, trailing},
[127]1260 })
[682]1261 case rpl_whospcrpl:
1262 dc, cmd := uc.currentPendingCommand("WHO")
1263 if cmd == nil {
1264 return fmt.Errorf("unexpected RPL_WHOSPCRPL: no matching pending WHO")
1265 } else if dc == nil {
1266 return nil
1267 }
1268
1269 // Only supported in single-upstream mode, so forward as-is
1270 dc.SendMessage(msg)
[127]1271 case irc.RPL_ENDOFWHO:
1272 var name string
1273 if err := parseMessageParams(msg, nil, &name); err != nil {
1274 return err
1275 }
1276
[682]1277 dc, cmd := uc.dequeueCommand("WHO")
1278 if cmd == nil {
1279 return fmt.Errorf("unexpected RPL_ENDOFWHO: no matching pending WHO")
1280 } else if dc == nil {
1281 return nil
1282 }
1283
1284 mask := "*"
1285 if len(cmd.Params) > 0 {
1286 mask = cmd.Params[0]
1287 }
1288 dc.SendMessage(&irc.Message{
1289 Prefix: dc.srv.prefix(),
1290 Command: irc.RPL_ENDOFWHO,
1291 Params: []string{dc.nick, mask, "End of /WHO list"},
[127]1292 })
[128]1293 case irc.RPL_WHOISUSER:
1294 var nick, username, host, realname string
1295 if err := parseMessageParams(msg, nil, &nick, &username, &host, nil, &realname); err != nil {
1296 return err
1297 }
1298
[161]1299 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1300 nick := dc.marshalEntity(uc.network, nick)
[128]1301 dc.SendMessage(&irc.Message{
1302 Prefix: dc.srv.prefix(),
1303 Command: irc.RPL_WHOISUSER,
1304 Params: []string{dc.nick, nick, username, host, "*", realname},
1305 })
1306 })
1307 case irc.RPL_WHOISSERVER:
1308 var nick, server, serverInfo string
1309 if err := parseMessageParams(msg, nil, &nick, &server, &serverInfo); err != nil {
1310 return err
1311 }
1312
[161]1313 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1314 nick := dc.marshalEntity(uc.network, nick)
[128]1315 dc.SendMessage(&irc.Message{
1316 Prefix: dc.srv.prefix(),
1317 Command: irc.RPL_WHOISSERVER,
1318 Params: []string{dc.nick, nick, server, serverInfo},
1319 })
1320 })
1321 case irc.RPL_WHOISOPERATOR:
1322 var nick string
1323 if err := parseMessageParams(msg, nil, &nick); err != nil {
1324 return err
1325 }
1326
[161]1327 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1328 nick := dc.marshalEntity(uc.network, nick)
[128]1329 dc.SendMessage(&irc.Message{
1330 Prefix: dc.srv.prefix(),
1331 Command: irc.RPL_WHOISOPERATOR,
1332 Params: []string{dc.nick, nick, "is an IRC operator"},
1333 })
1334 })
1335 case irc.RPL_WHOISIDLE:
1336 var nick string
1337 if err := parseMessageParams(msg, nil, &nick, nil); err != nil {
1338 return err
1339 }
1340
[161]1341 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1342 nick := dc.marshalEntity(uc.network, nick)
[128]1343 params := []string{dc.nick, nick}
1344 params = append(params, msg.Params[2:]...)
1345 dc.SendMessage(&irc.Message{
1346 Prefix: dc.srv.prefix(),
1347 Command: irc.RPL_WHOISIDLE,
1348 Params: params,
1349 })
1350 })
1351 case irc.RPL_WHOISCHANNELS:
1352 var nick, channelList string
1353 if err := parseMessageParams(msg, nil, &nick, &channelList); err != nil {
1354 return err
1355 }
[174]1356 channels := splitSpace(channelList)
[128]1357
[161]1358 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1359 nick := dc.marshalEntity(uc.network, nick)
[128]1360 channelList := make([]string, len(channels))
1361 for i, channel := range channels {
[139]1362 prefix, channel := uc.parseMembershipPrefix(channel)
[260]1363 channel = dc.marshalEntity(uc.network, channel)
[292]1364 channelList[i] = prefix.Format(dc) + channel
[128]1365 }
1366 channels := strings.Join(channelList, " ")
1367 dc.SendMessage(&irc.Message{
1368 Prefix: dc.srv.prefix(),
1369 Command: irc.RPL_WHOISCHANNELS,
1370 Params: []string{dc.nick, nick, channels},
1371 })
1372 })
1373 case irc.RPL_ENDOFWHOIS:
1374 var nick string
1375 if err := parseMessageParams(msg, nil, &nick); err != nil {
1376 return err
1377 }
1378
[161]1379 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
[260]1380 nick := dc.marshalEntity(uc.network, nick)
[128]1381 dc.SendMessage(&irc.Message{
1382 Prefix: dc.srv.prefix(),
1383 Command: irc.RPL_ENDOFWHOIS,
[142]1384 Params: []string{dc.nick, nick, "End of /WHOIS list"},
[128]1385 })
1386 })
[115]1387 case "INVITE":
[273]1388 var nick, channel string
[115]1389 if err := parseMessageParams(msg, &nick, &channel); err != nil {
1390 return err
1391 }
1392
[478]1393 weAreInvited := uc.isOurNick(nick)
[448]1394
[115]1395 uc.forEachDownstream(func(dc *downstreamConn) {
[448]1396 if !weAreInvited && !dc.caps["invite-notify"] {
1397 return
1398 }
[115]1399 dc.SendMessage(&irc.Message{
[260]1400 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
[115]1401 Command: "INVITE",
[260]1402 Params: []string{dc.marshalEntity(uc.network, nick), dc.marshalEntity(uc.network, channel)},
[115]1403 })
1404 })
[163]1405 case irc.RPL_INVITING:
[273]1406 var nick, channel string
[304]1407 if err := parseMessageParams(msg, nil, &nick, &channel); err != nil {
[163]1408 return err
1409 }
1410
1411 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1412 dc.SendMessage(&irc.Message{
1413 Prefix: dc.srv.prefix(),
1414 Command: irc.RPL_INVITING,
[260]1415 Params: []string{dc.nick, dc.marshalEntity(uc.network, nick), dc.marshalEntity(uc.network, channel)},
[163]1416 })
1417 })
[684]1418 case irc.RPL_MONONLINE, irc.RPL_MONOFFLINE:
1419 var targetsStr string
1420 if err := parseMessageParams(msg, nil, &targetsStr); err != nil {
1421 return err
1422 }
1423 targets := strings.Split(targetsStr, ",")
1424
1425 online := msg.Command == irc.RPL_MONONLINE
1426 for _, target := range targets {
1427 prefix := irc.ParsePrefix(target)
1428 uc.monitored.SetValue(prefix.Name, online)
1429 }
1430
1431 uc.forEachDownstream(func(dc *downstreamConn) {
1432 for _, target := range targets {
1433 prefix := irc.ParsePrefix(target)
1434 if dc.monitored.Has(prefix.Name) {
1435 dc.SendMessage(&irc.Message{
1436 Prefix: dc.srv.prefix(),
1437 Command: msg.Command,
1438 Params: []string{dc.nick, target},
1439 })
1440 }
1441 }
1442 })
1443 case irc.ERR_MONLISTFULL:
1444 var limit, targetsStr string
1445 if err := parseMessageParams(msg, nil, &limit, &targetsStr); err != nil {
1446 return err
1447 }
1448
1449 targets := strings.Split(targetsStr, ",")
1450 uc.forEachDownstream(func(dc *downstreamConn) {
1451 for _, target := range targets {
1452 if dc.monitored.Has(target) {
1453 dc.SendMessage(&irc.Message{
1454 Prefix: dc.srv.prefix(),
1455 Command: msg.Command,
1456 Params: []string{dc.nick, limit, target},
1457 })
1458 }
1459 }
1460 })
[272]1461 case irc.RPL_AWAY:
1462 var nick, reason string
1463 if err := parseMessageParams(msg, nil, &nick, &reason); err != nil {
1464 return err
1465 }
1466
[274]1467 uc.forEachDownstream(func(dc *downstreamConn) {
[272]1468 dc.SendMessage(&irc.Message{
1469 Prefix: dc.srv.prefix(),
1470 Command: irc.RPL_AWAY,
1471 Params: []string{dc.nick, dc.marshalEntity(uc.network, nick), reason},
1472 })
1473 })
[649]1474 case "AWAY", "ACCOUNT":
[276]1475 if msg.Prefix == nil {
1476 return fmt.Errorf("expected a prefix")
1477 }
1478
1479 uc.forEachDownstream(func(dc *downstreamConn) {
1480 dc.SendMessage(&irc.Message{
1481 Prefix: dc.marshalUserPrefix(uc.network, msg.Prefix),
[648]1482 Command: msg.Command,
1483 Params: msg.Params,
1484 })
1485 })
[300]1486 case irc.RPL_BANLIST, irc.RPL_INVITELIST, irc.RPL_EXCEPTLIST:
1487 var channel, mask string
1488 if err := parseMessageParams(msg, nil, &channel, &mask); err != nil {
1489 return err
1490 }
1491 var addNick, addTime string
1492 if len(msg.Params) >= 5 {
1493 addNick = msg.Params[3]
1494 addTime = msg.Params[4]
1495 }
1496
1497 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1498 channel := dc.marshalEntity(uc.network, channel)
1499
1500 var params []string
1501 if addNick != "" && addTime != "" {
1502 addNick := dc.marshalEntity(uc.network, addNick)
1503 params = []string{dc.nick, channel, mask, addNick, addTime}
1504 } else {
1505 params = []string{dc.nick, channel, mask}
1506 }
1507
1508 dc.SendMessage(&irc.Message{
1509 Prefix: dc.srv.prefix(),
1510 Command: msg.Command,
1511 Params: params,
1512 })
1513 })
1514 case irc.RPL_ENDOFBANLIST, irc.RPL_ENDOFINVITELIST, irc.RPL_ENDOFEXCEPTLIST:
1515 var channel, trailing string
1516 if err := parseMessageParams(msg, nil, &channel, &trailing); err != nil {
1517 return err
1518 }
1519
1520 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1521 upstreamChannel := dc.marshalEntity(uc.network, channel)
1522 dc.SendMessage(&irc.Message{
1523 Prefix: dc.srv.prefix(),
1524 Command: msg.Command,
1525 Params: []string{dc.nick, upstreamChannel, trailing},
1526 })
1527 })
[302]1528 case irc.ERR_UNKNOWNCOMMAND, irc.RPL_TRYAGAIN:
1529 var command, reason string
1530 if err := parseMessageParams(msg, nil, &command, &reason); err != nil {
1531 return err
1532 }
1533
[682]1534 if command == "LIST" || command == "WHO" {
1535 dc, _ := uc.dequeueCommand(command)
1536 if dc != nil && downstreamID == 0 {
1537 downstreamID = dc.id
1538 }
[302]1539 }
1540
[355]1541 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1542 dc.SendMessage(&irc.Message{
1543 Prefix: uc.srv.prefix(),
1544 Command: msg.Command,
1545 Params: []string{dc.nick, command, reason},
[302]1546 })
[355]1547 })
[155]1548 case "ACK":
1549 // Ignore
[198]1550 case irc.RPL_NOWAWAY, irc.RPL_UNAWAY:
1551 // Ignore
[16]1552 case irc.RPL_YOURHOST, irc.RPL_CREATED:
[14]1553 // Ignore
1554 case irc.RPL_LUSERCLIENT, irc.RPL_LUSEROP, irc.RPL_LUSERUNKNOWN, irc.RPL_LUSERCHANNELS, irc.RPL_LUSERME:
[561]1555 fallthrough
1556 case irc.RPL_STATSVLINE, rpl_statsping, irc.RPL_STATSBLINE, irc.RPL_STATSDLINE:
1557 fallthrough
1558 case rpl_localusers, rpl_globalusers:
1559 fallthrough
[478]1560 case irc.RPL_MOTDSTART, irc.RPL_MOTD:
[561]1561 // Ignore these messages if they're part of the initial registration
1562 // message burst. Forward them if the user explicitly asked for them.
[552]1563 if !uc.gotMotd {
1564 return nil
1565 }
1566
[553]1567 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1568 dc.SendMessage(&irc.Message{
1569 Prefix: uc.srv.prefix(),
[552]1570 Command: msg.Command,
[553]1571 Params: msg.Params,
[552]1572 })
1573 })
[177]1574 case irc.RPL_LISTSTART:
1575 // Ignore
[390]1576 case "ERROR":
1577 var text string
1578 if err := parseMessageParams(msg, &text); err != nil {
1579 return err
1580 }
1581 return fmt.Errorf("fatal server error: %v", text)
[389]1582 case irc.ERR_PASSWDMISMATCH, irc.ERR_ERRONEUSNICKNAME, irc.ERR_NICKNAMEINUSE, irc.ERR_NICKCOLLISION, irc.ERR_UNAVAILRESOURCE, irc.ERR_NOPERMFORHOST, irc.ERR_YOUREBANNEDCREEP:
[342]1583 if !uc.registered {
[399]1584 text := msg.Params[len(msg.Params)-1]
1585 return registrationError(text)
[342]1586 }
1587 fallthrough
[13]1588 default:
[95]1589 uc.logger.Printf("unhandled message: %v", msg)
[355]1590
1591 uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
1592 // best effort marshaling for unknown messages, replies and errors:
1593 // most numerics start with the user nick, marshal it if that's the case
1594 // otherwise, conservately keep the params without marshaling
1595 params := msg.Params
1596 if _, err := strconv.Atoi(msg.Command); err == nil { // numeric
1597 if len(msg.Params) > 0 && isOurNick(uc.network, msg.Params[0]) {
1598 params[0] = dc.nick
[302]1599 }
[355]1600 }
1601 dc.SendMessage(&irc.Message{
1602 Prefix: uc.srv.prefix(),
1603 Command: msg.Command,
1604 Params: params,
[302]1605 })
[355]1606 })
[13]1607 }
[14]1608 return nil
[13]1609}
1610
[499]1611func (uc *upstreamConn) handleDetachedMessage(ch *Channel, msg *irc.Message) {
1612 if uc.network.detachedMessageNeedsRelay(ch, msg) {
[435]1613 uc.forEachDownstream(func(dc *downstreamConn) {
[499]1614 dc.relayDetachedMessage(uc.network, msg)
[435]1615 })
1616 }
[499]1617 if ch.ReattachOn == FilterMessage || (ch.ReattachOn == FilterHighlight && uc.network.isHighlight(msg)) {
[435]1618 uc.network.attach(ch)
[652]1619 if err := uc.srv.db.StoreChannel(context.TODO(), uc.network.ID, ch); err != nil {
[435]1620 uc.logger.Printf("failed to update channel %q: %v", ch.Name, err)
1621 }
1622 }
1623}
1624
[458]1625func (uc *upstreamConn) handleChanModes(s string) error {
1626 parts := strings.SplitN(s, ",", 5)
1627 if len(parts) < 4 {
1628 return fmt.Errorf("malformed ISUPPORT CHANMODES value: %v", s)
1629 }
1630 modes := make(map[byte]channelModeType)
1631 for i, mt := range []channelModeType{modeTypeA, modeTypeB, modeTypeC, modeTypeD} {
1632 for j := 0; j < len(parts[i]); j++ {
1633 mode := parts[i][j]
1634 modes[mode] = mt
1635 }
1636 }
1637 uc.availableChannelModes = modes
1638 return nil
1639}
1640
1641func (uc *upstreamConn) handleMemberships(s string) error {
1642 if s == "" {
1643 uc.availableMemberships = nil
1644 return nil
1645 }
1646
1647 if s[0] != '(' {
1648 return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", s)
1649 }
1650 sep := strings.IndexByte(s, ')')
1651 if sep < 0 || len(s) != sep*2 {
1652 return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", s)
1653 }
1654 memberships := make([]membership, len(s)/2-1)
1655 for i := range memberships {
1656 memberships[i] = membership{
1657 Mode: s[i+1],
1658 Prefix: s[sep+i+1],
1659 }
1660 }
1661 uc.availableMemberships = memberships
1662 return nil
1663}
1664
[281]1665func (uc *upstreamConn) handleSupportedCaps(capsStr string) {
1666 caps := strings.Fields(capsStr)
1667 for _, s := range caps {
1668 kv := strings.SplitN(s, "=", 2)
1669 k := strings.ToLower(kv[0])
1670 var v string
1671 if len(kv) == 2 {
1672 v = kv[1]
1673 }
1674 uc.supportedCaps[k] = v
1675 }
1676}
1677
1678func (uc *upstreamConn) requestCaps() {
1679 var requestCaps []string
[282]1680 for c := range permanentUpstreamCaps {
[281]1681 if _, ok := uc.supportedCaps[c]; ok && !uc.caps[c] {
1682 requestCaps = append(requestCaps, c)
1683 }
1684 }
1685
1686 if uc.requestSASL() && !uc.caps["sasl"] {
1687 requestCaps = append(requestCaps, "sasl")
1688 }
1689
[282]1690 if len(requestCaps) == 0 {
1691 return
1692 }
1693
1694 uc.SendMessage(&irc.Message{
1695 Command: "CAP",
1696 Params: []string{"REQ", strings.Join(requestCaps, " ")},
1697 })
1698}
1699
1700func (uc *upstreamConn) requestSASL() bool {
1701 if uc.network.SASL.Mechanism == "" {
1702 return false
1703 }
1704
1705 v, ok := uc.supportedCaps["sasl"]
1706 if !ok {
1707 return false
1708 }
1709 if v != "" {
1710 mechanisms := strings.Split(v, ",")
1711 found := false
1712 for _, mech := range mechanisms {
1713 if strings.EqualFold(mech, uc.network.SASL.Mechanism) {
1714 found = true
1715 break
1716 }
1717 }
1718 if !found {
1719 return false
1720 }
1721 }
1722
1723 return true
1724}
1725
1726func (uc *upstreamConn) handleCapAck(name string, ok bool) error {
1727 uc.caps[name] = ok
1728
1729 switch name {
1730 case "sasl":
1731 if !ok {
1732 uc.logger.Printf("server refused to acknowledge the SASL capability")
1733 return nil
1734 }
1735
1736 auth := &uc.network.SASL
1737 switch auth.Mechanism {
1738 case "PLAIN":
1739 uc.logger.Printf("starting SASL PLAIN authentication with username %q", auth.Plain.Username)
1740 uc.saslClient = sasl.NewPlainClient("", auth.Plain.Username, auth.Plain.Password)
[307]1741 case "EXTERNAL":
1742 uc.logger.Printf("starting SASL EXTERNAL authentication")
1743 uc.saslClient = sasl.NewExternalClient("")
[282]1744 default:
1745 return fmt.Errorf("unsupported SASL mechanism %q", name)
1746 }
1747
[281]1748 uc.SendMessage(&irc.Message{
[282]1749 Command: "AUTHENTICATE",
1750 Params: []string{auth.Mechanism},
[281]1751 })
[282]1752 default:
1753 if permanentUpstreamCaps[name] {
1754 break
1755 }
1756 uc.logger.Printf("received CAP ACK/NAK for a cap we don't support: %v", name)
[281]1757 }
[282]1758 return nil
[281]1759}
1760
[174]1761func splitSpace(s string) []string {
1762 return strings.FieldsFunc(s, func(r rune) bool {
1763 return r == ' '
1764 })
1765}
1766
[55]1767func (uc *upstreamConn) register() {
[664]1768 uc.nick = GetNick(&uc.user.User, &uc.network.Network)
[478]1769 uc.nickCM = uc.network.casemap(uc.nick)
[674]1770 uc.username = GetUsername(&uc.user.User, &uc.network.Network)
[568]1771 uc.realname = GetRealname(&uc.user.User, &uc.network.Network)
[77]1772
[60]1773 uc.SendMessage(&irc.Message{
[92]1774 Command: "CAP",
1775 Params: []string{"LS", "302"},
1776 })
1777
[93]1778 if uc.network.Pass != "" {
1779 uc.SendMessage(&irc.Message{
1780 Command: "PASS",
1781 Params: []string{uc.network.Pass},
1782 })
1783 }
1784
[92]1785 uc.SendMessage(&irc.Message{
[13]1786 Command: "NICK",
[69]1787 Params: []string{uc.nick},
[60]1788 })
1789 uc.SendMessage(&irc.Message{
[13]1790 Command: "USER",
[77]1791 Params: []string{uc.username, "0", "*", uc.realname},
[60]1792 })
[44]1793}
[13]1794
[197]1795func (uc *upstreamConn) runUntilRegistered() error {
1796 for !uc.registered {
[212]1797 msg, err := uc.ReadMessage()
[197]1798 if err != nil {
1799 return fmt.Errorf("failed to read message: %v", err)
1800 }
1801
1802 if err := uc.handleMessage(msg); err != nil {
[399]1803 if _, ok := err.(registrationError); ok {
1804 return err
1805 } else {
1806 msg.Tags = nil // prevent message tags from cluttering logs
1807 return fmt.Errorf("failed to handle message %q: %v", msg, err)
1808 }
[197]1809 }
1810 }
1811
[263]1812 for _, command := range uc.network.ConnectCommands {
1813 m, err := irc.ParseMessage(command)
1814 if err != nil {
1815 uc.logger.Printf("failed to parse connect command %q: %v", command, err)
1816 } else {
1817 uc.SendMessage(m)
1818 }
1819 }
1820
[197]1821 return nil
1822}
1823
[165]1824func (uc *upstreamConn) readMessages(ch chan<- event) error {
[13]1825 for {
[210]1826 msg, err := uc.ReadMessage()
[655]1827 if errors.Is(err, io.EOF) {
[13]1828 break
1829 } else if err != nil {
1830 return fmt.Errorf("failed to read IRC command: %v", err)
1831 }
1832
[165]1833 ch <- eventUpstreamMessage{msg, uc}
[13]1834 }
1835
[45]1836 return nil
[13]1837}
[60]1838
[303]1839func (uc *upstreamConn) SendMessage(msg *irc.Message) {
1840 if !uc.caps["message-tags"] {
1841 msg = msg.Copy()
1842 msg.Tags = nil
1843 }
1844
1845 uc.conn.SendMessage(msg)
1846}
1847
[176]1848func (uc *upstreamConn) SendMessageLabeled(downstreamID uint64, msg *irc.Message) {
[278]1849 if uc.caps["labeled-response"] {
[155]1850 if msg.Tags == nil {
1851 msg.Tags = make(map[string]irc.TagValue)
1852 }
[176]1853 msg.Tags["label"] = irc.TagValue(fmt.Sprintf("sd-%d-%d", downstreamID, uc.nextLabelID))
[161]1854 uc.nextLabelID++
[155]1855 }
1856 uc.SendMessage(msg)
1857}
[178]1858
[428]1859// appendLog appends a message to the log file.
1860//
1861// The internal message ID is returned. If the message isn't recorded in the
1862// log file, an empty string is returned.
1863func (uc *upstreamConn) appendLog(entity string, msg *irc.Message) (msgID string) {
[423]1864 if uc.user.msgStore == nil {
[428]1865 return ""
[178]1866 }
[486]1867
[563]1868 // Don't store messages with a server mask target
1869 if strings.HasPrefix(entity, "$") {
1870 return ""
1871 }
1872
[486]1873 entityCM := uc.network.casemap(entity)
1874 if entityCM == "nickserv" {
[468]1875 // The messages sent/received from NickServ may contain
1876 // security-related information (like passwords). Don't store these.
1877 return ""
1878 }
[215]1879
[485]1880 if !uc.network.delivered.HasTarget(entity) {
[482]1881 // This is the first message we receive from this target. Save the last
1882 // message ID in delivery receipts, so that we can send the new message
1883 // in the backlog if an offline client reconnects.
[666]1884 lastID, err := uc.user.msgStore.LastMsgID(&uc.network.Network, entityCM, time.Now())
[409]1885 if err != nil {
1886 uc.logger.Printf("failed to log message: failed to get last message ID: %v", err)
[428]1887 return ""
[409]1888 }
1889
[489]1890 uc.network.delivered.ForEachClient(func(clientName string) {
[485]1891 uc.network.delivered.StoreID(entity, clientName, lastID)
[489]1892 })
[253]1893 }
1894
[666]1895 msgID, err := uc.user.msgStore.Append(&uc.network.Network, entityCM, msg)
[409]1896 if err != nil {
1897 uc.logger.Printf("failed to log message: %v", err)
[428]1898 return ""
[409]1899 }
[406]1900
[428]1901 return msgID
[253]1902}
1903
[409]1904// produce appends a message to the logs and forwards it to connected downstream
1905// connections.
[245]1906//
1907// If origin is not nil and origin doesn't support echo-message, the message is
1908// forwarded to all connections except origin.
[239]1909func (uc *upstreamConn) produce(target string, msg *irc.Message, origin *downstreamConn) {
[428]1910 var msgID string
[239]1911 if target != "" {
[428]1912 msgID = uc.appendLog(target, msg)
[239]1913 }
1914
[284]1915 // Don't forward messages if it's a detached channel
[478]1916 ch := uc.network.channels.Value(target)
[499]1917 detached := ch != nil && ch.Detached
[284]1918
[227]1919 uc.forEachDownstream(func(dc *downstreamConn) {
[499]1920 if !detached && (dc != origin || dc.caps["echo-message"]) {
[428]1921 dc.sendMessageWithID(dc.marshalMessage(msg, uc.network), msgID)
1922 } else {
1923 dc.advanceMessageWithID(msg, msgID)
[238]1924 }
[227]1925 })
[226]1926}
1927
[198]1928func (uc *upstreamConn) updateAway() {
1929 away := true
1930 uc.forEachDownstream(func(*downstreamConn) {
1931 away = false
1932 })
1933 if away == uc.away {
1934 return
1935 }
1936 if away {
1937 uc.SendMessage(&irc.Message{
1938 Command: "AWAY",
1939 Params: []string{"Auto away"},
1940 })
1941 } else {
1942 uc.SendMessage(&irc.Message{
1943 Command: "AWAY",
1944 })
1945 }
1946 uc.away = away
1947}
[435]1948
1949func (uc *upstreamConn) updateChannelAutoDetach(name string) {
[478]1950 uch := uc.channels.Value(name)
1951 if uch == nil {
1952 return
[435]1953 }
[478]1954 ch := uc.network.channels.Value(name)
1955 if ch == nil || ch.Detached {
1956 return
1957 }
1958 uch.updateAutoDetach(ch.DetachAfter)
[435]1959}
[684]1960
1961func (uc *upstreamConn) updateMonitor() {
1962 add := make(map[string]struct{})
1963 var addList []string
1964 seen := make(map[string]struct{})
1965 uc.forEachDownstream(func(dc *downstreamConn) {
1966 for targetCM := range dc.monitored.innerMap {
1967 if !uc.monitored.Has(targetCM) {
1968 if _, ok := add[targetCM]; !ok {
1969 addList = append(addList, targetCM)
1970 }
1971 add[targetCM] = struct{}{}
1972 } else {
1973 seen[targetCM] = struct{}{}
1974 }
1975 }
1976 })
1977
1978 removeAll := true
1979 var removeList []string
1980 for targetCM, entry := range uc.monitored.innerMap {
1981 if _, ok := seen[targetCM]; ok {
1982 removeAll = false
1983 } else {
1984 removeList = append(removeList, entry.originalKey)
1985 }
1986 }
1987
1988 // TODO: better handle the case where len(uc.monitored) + len(addList)
1989 // exceeds the limit, probably by immediately sending ERR_MONLISTFULL?
1990
1991 if removeAll && len(addList) == 0 && len(removeList) > 0 {
1992 // Optimization when the last MONITOR-aware downstream disconnects
1993 uc.SendMessage(&irc.Message{
1994 Command: "MONITOR",
1995 Params: []string{"C"},
1996 })
1997 } else {
1998 msgs := generateMonitor("-", removeList)
1999 msgs = append(msgs, generateMonitor("+", addList)...)
2000 for _, msg := range msgs {
2001 uc.SendMessage(msg)
2002 }
2003 }
2004
2005 for _, target := range removeList {
2006 uc.monitored.Delete(target)
2007 }
2008}
Note: See TracBrowser for help on using the repository browser.