source: code/trunk/upstream.go@ 724

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

Add support for post-connection-registration upstream SASL auth

Once the downstream connection has logged in with their bouncer
credentials, allow them to issue more SASL auths which will be
redirected to the upstream network. This allows downstream clients
to provide UIs to login to transparently login to upstream networks.

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