source: code/trunk/downstream.go@ 725

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

Remove sasl cap after registration if network doesn't support it

This will stop clients from trying to issue AUTHENTICATE requests
after connection registration.

File size: 71.0 KB
Line 
1package soju
2
3import (
4 "context"
5 "crypto/tls"
6 "encoding/base64"
7 "errors"
8 "fmt"
9 "io"
10 "net"
11 "strconv"
12 "strings"
13 "time"
14
15 "github.com/emersion/go-sasl"
16 "golang.org/x/crypto/bcrypt"
17 "gopkg.in/irc.v3"
18)
19
20type ircError struct {
21 Message *irc.Message
22}
23
24func (err ircError) Error() string {
25 return err.Message.String()
26}
27
28func newUnknownCommandError(cmd string) ircError {
29 return ircError{&irc.Message{
30 Command: irc.ERR_UNKNOWNCOMMAND,
31 Params: []string{
32 "*",
33 cmd,
34 "Unknown command",
35 },
36 }}
37}
38
39func newNeedMoreParamsError(cmd string) ircError {
40 return ircError{&irc.Message{
41 Command: irc.ERR_NEEDMOREPARAMS,
42 Params: []string{
43 "*",
44 cmd,
45 "Not enough parameters",
46 },
47 }}
48}
49
50func newChatHistoryError(subcommand string, target string) ircError {
51 return ircError{&irc.Message{
52 Command: "FAIL",
53 Params: []string{"CHATHISTORY", "MESSAGE_ERROR", subcommand, target, "Messages could not be retrieved"},
54 }}
55}
56
57var errAuthFailed = ircError{&irc.Message{
58 Command: irc.ERR_PASSWDMISMATCH,
59 Params: []string{"*", "Invalid username or password"},
60}}
61
62func parseBouncerNetID(subcommand, s string) (int64, error) {
63 id, err := strconv.ParseInt(s, 10, 64)
64 if err != nil {
65 return 0, ircError{&irc.Message{
66 Command: "FAIL",
67 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, s, "Invalid network ID"},
68 }}
69 }
70 return id, nil
71}
72
73func fillNetworkAddrAttrs(attrs irc.Tags, network *Network) {
74 u, err := network.URL()
75 if err != nil {
76 return
77 }
78
79 hasHostPort := true
80 switch u.Scheme {
81 case "ircs":
82 attrs["tls"] = irc.TagValue("1")
83 case "irc+insecure":
84 attrs["tls"] = irc.TagValue("0")
85 default: // e.g. unix://
86 hasHostPort = false
87 }
88 if host, port, err := net.SplitHostPort(u.Host); err == nil && hasHostPort {
89 attrs["host"] = irc.TagValue(host)
90 attrs["port"] = irc.TagValue(port)
91 } else if hasHostPort {
92 attrs["host"] = irc.TagValue(u.Host)
93 }
94}
95
96func getNetworkAttrs(network *network) irc.Tags {
97 state := "disconnected"
98 if uc := network.conn; uc != nil {
99 state = "connected"
100 }
101
102 attrs := irc.Tags{
103 "name": irc.TagValue(network.GetName()),
104 "state": irc.TagValue(state),
105 "nickname": irc.TagValue(GetNick(&network.user.User, &network.Network)),
106 }
107
108 if network.Username != "" {
109 attrs["username"] = irc.TagValue(network.Username)
110 }
111 if realname := GetRealname(&network.user.User, &network.Network); realname != "" {
112 attrs["realname"] = irc.TagValue(realname)
113 }
114
115 fillNetworkAddrAttrs(attrs, &network.Network)
116
117 return attrs
118}
119
120func networkAddrFromAttrs(attrs irc.Tags) string {
121 host, ok := attrs.GetTag("host")
122 if !ok {
123 return ""
124 }
125
126 addr := host
127 if port, ok := attrs.GetTag("port"); ok {
128 addr += ":" + port
129 }
130
131 if tlsStr, ok := attrs.GetTag("tls"); ok && tlsStr == "0" {
132 addr = "irc+insecure://" + tlsStr
133 }
134
135 return addr
136}
137
138func updateNetworkAttrs(record *Network, attrs irc.Tags, subcommand string) error {
139 addrAttrs := irc.Tags{}
140 fillNetworkAddrAttrs(addrAttrs, record)
141
142 updateAddr := false
143 for k, v := range attrs {
144 s := string(v)
145 switch k {
146 case "host", "port", "tls":
147 updateAddr = true
148 addrAttrs[k] = v
149 case "name":
150 record.Name = s
151 case "nickname":
152 record.Nick = s
153 case "username":
154 record.Username = s
155 case "realname":
156 record.Realname = s
157 case "pass":
158 record.Pass = s
159 default:
160 return ircError{&irc.Message{
161 Command: "FAIL",
162 Params: []string{"BOUNCER", "UNKNOWN_ATTRIBUTE", subcommand, k, "Unknown attribute"},
163 }}
164 }
165 }
166
167 if updateAddr {
168 record.Addr = networkAddrFromAttrs(addrAttrs)
169 if record.Addr == "" {
170 return ircError{&irc.Message{
171 Command: "FAIL",
172 Params: []string{"BOUNCER", "NEED_ATTRIBUTE", subcommand, "host", "Missing required host attribute"},
173 }}
174 }
175 }
176
177 return nil
178}
179
180// ' ' and ':' break the IRC message wire format, '@' and '!' break prefixes,
181// '*' and '?' break masks, '$' breaks server masks in PRIVMSG/NOTICE,
182// "*" is the reserved nickname for registration
183const illegalNickChars = " :@!*?$"
184
185// permanentDownstreamCaps is the list of always-supported downstream
186// capabilities.
187var permanentDownstreamCaps = map[string]string{
188 "batch": "",
189 "cap-notify": "",
190 "echo-message": "",
191 "invite-notify": "",
192 "message-tags": "",
193 "server-time": "",
194 "setname": "",
195
196 "soju.im/bouncer-networks": "",
197 "soju.im/bouncer-networks-notify": "",
198}
199
200// needAllDownstreamCaps is the list of downstream capabilities that
201// require support from all upstreams to be enabled
202var needAllDownstreamCaps = map[string]string{
203 "account-notify": "",
204 "account-tag": "",
205 "away-notify": "",
206 "extended-join": "",
207 "multi-prefix": "",
208
209 "draft/extended-monitor": "",
210}
211
212// passthroughIsupport is the set of ISUPPORT tokens that are directly passed
213// through from the upstream server to downstream clients.
214//
215// This is only effective in single-upstream mode.
216var passthroughIsupport = map[string]bool{
217 "AWAYLEN": true,
218 "BOT": true,
219 "CHANLIMIT": true,
220 "CHANMODES": true,
221 "CHANNELLEN": true,
222 "CHANTYPES": true,
223 "CLIENTTAGDENY": true,
224 "ELIST": true,
225 "EXCEPTS": true,
226 "EXTBAN": true,
227 "HOSTLEN": true,
228 "INVEX": true,
229 "KICKLEN": true,
230 "MAXLIST": true,
231 "MAXTARGETS": true,
232 "MODES": true,
233 "MONITOR": true,
234 "NAMELEN": true,
235 "NETWORK": true,
236 "NICKLEN": true,
237 "PREFIX": true,
238 "SAFELIST": true,
239 "TARGMAX": true,
240 "TOPICLEN": true,
241 "USERLEN": true,
242 "UTF8ONLY": true,
243 "WHOX": true,
244}
245
246type downstreamSASL struct {
247 server sasl.Server
248 plainUsername, plainPassword string
249}
250
251type downstreamConn struct {
252 conn
253
254 id uint64
255
256 registered bool
257 user *user
258 nick string
259 nickCM string
260 rawUsername string
261 networkName string
262 clientName string
263 realname string
264 hostname string
265 account string // RPL_LOGGEDIN/OUT state
266 password string // empty after authentication
267 network *network // can be nil
268 isMultiUpstream bool
269
270 negotiatingCaps bool
271 capVersion int
272 supportedCaps map[string]string
273 caps map[string]bool
274 sasl *downstreamSASL
275
276 lastBatchRef uint64
277
278 monitored casemapMap
279}
280
281func newDownstreamConn(srv *Server, ic ircConn, id uint64) *downstreamConn {
282 remoteAddr := ic.RemoteAddr().String()
283 logger := &prefixLogger{srv.Logger, fmt.Sprintf("downstream %q: ", remoteAddr)}
284 options := connOptions{Logger: logger}
285 dc := &downstreamConn{
286 conn: *newConn(srv, ic, &options),
287 id: id,
288 nick: "*",
289 nickCM: "*",
290 supportedCaps: make(map[string]string),
291 caps: make(map[string]bool),
292 monitored: newCasemapMap(0),
293 }
294 dc.hostname = remoteAddr
295 if host, _, err := net.SplitHostPort(dc.hostname); err == nil {
296 dc.hostname = host
297 }
298 for k, v := range permanentDownstreamCaps {
299 dc.supportedCaps[k] = v
300 }
301 dc.supportedCaps["sasl"] = "PLAIN"
302 // TODO: this is racy, we should only enable chathistory after
303 // authentication and then check that user.msgStore implements
304 // chatHistoryMessageStore
305 if srv.Config().LogPath != "" {
306 dc.supportedCaps["draft/chathistory"] = ""
307 }
308 return dc
309}
310
311func (dc *downstreamConn) prefix() *irc.Prefix {
312 return &irc.Prefix{
313 Name: dc.nick,
314 User: dc.user.Username,
315 Host: dc.hostname,
316 }
317}
318
319func (dc *downstreamConn) forEachNetwork(f func(*network)) {
320 if dc.network != nil {
321 f(dc.network)
322 } else if dc.isMultiUpstream {
323 dc.user.forEachNetwork(f)
324 }
325}
326
327func (dc *downstreamConn) forEachUpstream(f func(*upstreamConn)) {
328 if dc.network == nil && !dc.isMultiUpstream {
329 return
330 }
331 dc.user.forEachUpstream(func(uc *upstreamConn) {
332 if dc.network != nil && uc.network != dc.network {
333 return
334 }
335 f(uc)
336 })
337}
338
339// upstream returns the upstream connection, if any. If there are zero or if
340// there are multiple upstream connections, it returns nil.
341func (dc *downstreamConn) upstream() *upstreamConn {
342 if dc.network == nil {
343 return nil
344 }
345 return dc.network.conn
346}
347
348func isOurNick(net *network, nick string) bool {
349 // TODO: this doesn't account for nick changes
350 if net.conn != nil {
351 return net.casemap(nick) == net.conn.nickCM
352 }
353 // We're not currently connected to the upstream connection, so we don't
354 // know whether this name is our nickname. Best-effort: use the network's
355 // configured nickname and hope it was the one being used when we were
356 // connected.
357 return net.casemap(nick) == net.casemap(GetNick(&net.user.User, &net.Network))
358}
359
360// marshalEntity converts an upstream entity name (ie. channel or nick) into a
361// downstream entity name.
362//
363// This involves adding a "/<network>" suffix if the entity isn't the current
364// user.
365func (dc *downstreamConn) marshalEntity(net *network, name string) string {
366 if isOurNick(net, name) {
367 return dc.nick
368 }
369 name = partialCasemap(net.casemap, name)
370 if dc.network != nil {
371 if dc.network != net {
372 panic("soju: tried to marshal an entity for another network")
373 }
374 return name
375 }
376 return name + "/" + net.GetName()
377}
378
379func (dc *downstreamConn) marshalUserPrefix(net *network, prefix *irc.Prefix) *irc.Prefix {
380 if isOurNick(net, prefix.Name) {
381 return dc.prefix()
382 }
383 prefix.Name = partialCasemap(net.casemap, prefix.Name)
384 if dc.network != nil {
385 if dc.network != net {
386 panic("soju: tried to marshal a user prefix for another network")
387 }
388 return prefix
389 }
390 return &irc.Prefix{
391 Name: prefix.Name + "/" + net.GetName(),
392 User: prefix.User,
393 Host: prefix.Host,
394 }
395}
396
397// unmarshalEntityNetwork converts a downstream entity name (ie. channel or
398// nick) into an upstream entity name.
399//
400// This involves removing the "/<network>" suffix.
401func (dc *downstreamConn) unmarshalEntityNetwork(name string) (*network, string, error) {
402 if dc.network != nil {
403 return dc.network, name, nil
404 }
405
406 var net *network
407 if i := strings.LastIndexByte(name, '/'); i >= 0 {
408 network := name[i+1:]
409 name = name[:i]
410
411 for _, n := range dc.user.networks {
412 if network == n.GetName() {
413 net = n
414 break
415 }
416 }
417 }
418
419 if net == nil {
420 return nil, "", ircError{&irc.Message{
421 Command: irc.ERR_NOSUCHCHANNEL,
422 Params: []string{name, "Missing network suffix in name"},
423 }}
424 }
425
426 return net, name, nil
427}
428
429// unmarshalEntity is the same as unmarshalEntityNetwork, but returns the
430// upstream connection and fails if the upstream is disconnected.
431func (dc *downstreamConn) unmarshalEntity(name string) (*upstreamConn, string, error) {
432 net, name, err := dc.unmarshalEntityNetwork(name)
433 if err != nil {
434 return nil, "", err
435 }
436
437 if net.conn == nil {
438 return nil, "", ircError{&irc.Message{
439 Command: irc.ERR_NOSUCHCHANNEL,
440 Params: []string{name, "Disconnected from upstream network"},
441 }}
442 }
443
444 return net.conn, name, nil
445}
446
447func (dc *downstreamConn) unmarshalText(uc *upstreamConn, text string) string {
448 if dc.upstream() != nil {
449 return text
450 }
451 // TODO: smarter parsing that ignores URLs
452 return strings.ReplaceAll(text, "/"+uc.network.GetName(), "")
453}
454
455func (dc *downstreamConn) ReadMessage() (*irc.Message, error) {
456 msg, err := dc.conn.ReadMessage()
457 if err != nil {
458 return nil, err
459 }
460 dc.srv.metrics.downstreamInMessagesTotal.Inc()
461 return msg, nil
462}
463
464func (dc *downstreamConn) readMessages(ch chan<- event) error {
465 for {
466 msg, err := dc.ReadMessage()
467 if errors.Is(err, io.EOF) {
468 break
469 } else if err != nil {
470 return fmt.Errorf("failed to read IRC command: %v", err)
471 }
472
473 ch <- eventDownstreamMessage{msg, dc}
474 }
475
476 return nil
477}
478
479// SendMessage sends an outgoing message.
480//
481// This can only called from the user goroutine.
482func (dc *downstreamConn) SendMessage(msg *irc.Message) {
483 if !dc.caps["message-tags"] {
484 if msg.Command == "TAGMSG" {
485 return
486 }
487 msg = msg.Copy()
488 for name := range msg.Tags {
489 supported := false
490 switch name {
491 case "time":
492 supported = dc.caps["server-time"]
493 case "account":
494 supported = dc.caps["account"]
495 }
496 if !supported {
497 delete(msg.Tags, name)
498 }
499 }
500 }
501 if !dc.caps["batch"] && msg.Tags["batch"] != "" {
502 msg = msg.Copy()
503 delete(msg.Tags, "batch")
504 }
505 if msg.Command == "JOIN" && !dc.caps["extended-join"] {
506 msg.Params = msg.Params[:1]
507 }
508 if msg.Command == "SETNAME" && !dc.caps["setname"] {
509 return
510 }
511 if msg.Command == "AWAY" && !dc.caps["away-notify"] {
512 return
513 }
514 if msg.Command == "ACCOUNT" && !dc.caps["account-notify"] {
515 return
516 }
517
518 dc.srv.metrics.downstreamOutMessagesTotal.Inc()
519 dc.conn.SendMessage(msg)
520}
521
522func (dc *downstreamConn) SendBatch(typ string, params []string, tags irc.Tags, f func(batchRef irc.TagValue)) {
523 dc.lastBatchRef++
524 ref := fmt.Sprintf("%v", dc.lastBatchRef)
525
526 if dc.caps["batch"] {
527 dc.SendMessage(&irc.Message{
528 Tags: tags,
529 Prefix: dc.srv.prefix(),
530 Command: "BATCH",
531 Params: append([]string{"+" + ref, typ}, params...),
532 })
533 }
534
535 f(irc.TagValue(ref))
536
537 if dc.caps["batch"] {
538 dc.SendMessage(&irc.Message{
539 Prefix: dc.srv.prefix(),
540 Command: "BATCH",
541 Params: []string{"-" + ref},
542 })
543 }
544}
545
546// sendMessageWithID sends an outgoing message with the specified internal ID.
547func (dc *downstreamConn) sendMessageWithID(msg *irc.Message, id string) {
548 dc.SendMessage(msg)
549
550 if id == "" || !dc.messageSupportsBacklog(msg) {
551 return
552 }
553
554 dc.sendPing(id)
555}
556
557// advanceMessageWithID advances history to the specified message ID without
558// sending a message. This is useful e.g. for self-messages when echo-message
559// isn't enabled.
560func (dc *downstreamConn) advanceMessageWithID(msg *irc.Message, id string) {
561 if id == "" || !dc.messageSupportsBacklog(msg) {
562 return
563 }
564
565 dc.sendPing(id)
566}
567
568// ackMsgID acknowledges that a message has been received.
569func (dc *downstreamConn) ackMsgID(id string) {
570 netID, entity, err := parseMsgID(id, nil)
571 if err != nil {
572 dc.logger.Printf("failed to ACK message ID %q: %v", id, err)
573 return
574 }
575
576 network := dc.user.getNetworkByID(netID)
577 if network == nil {
578 return
579 }
580
581 network.delivered.StoreID(entity, dc.clientName, id)
582}
583
584func (dc *downstreamConn) sendPing(msgID string) {
585 token := "soju-msgid-" + msgID
586 dc.SendMessage(&irc.Message{
587 Command: "PING",
588 Params: []string{token},
589 })
590}
591
592func (dc *downstreamConn) handlePong(token string) {
593 if !strings.HasPrefix(token, "soju-msgid-") {
594 dc.logger.Printf("received unrecognized PONG token %q", token)
595 return
596 }
597 msgID := strings.TrimPrefix(token, "soju-msgid-")
598 dc.ackMsgID(msgID)
599}
600
601// marshalMessage re-formats a message coming from an upstream connection so
602// that it's suitable for being sent on this downstream connection. Only
603// messages that may appear in logs are supported, except MODE messages which
604// may only appear in single-upstream mode.
605func (dc *downstreamConn) marshalMessage(msg *irc.Message, net *network) *irc.Message {
606 msg = msg.Copy()
607 msg.Prefix = dc.marshalUserPrefix(net, msg.Prefix)
608
609 if dc.network != nil {
610 return msg
611 }
612
613 switch msg.Command {
614 case "PRIVMSG", "NOTICE", "TAGMSG":
615 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
616 case "NICK":
617 // Nick change for another user
618 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
619 case "JOIN", "PART":
620 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
621 case "KICK":
622 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
623 msg.Params[1] = dc.marshalEntity(net, msg.Params[1])
624 case "TOPIC":
625 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
626 case "QUIT", "SETNAME":
627 // This space is intentionally left blank
628 default:
629 panic(fmt.Sprintf("unexpected %q message", msg.Command))
630 }
631
632 return msg
633}
634
635func (dc *downstreamConn) handleMessage(ctx context.Context, msg *irc.Message) error {
636 ctx, cancel := dc.conn.NewContext(ctx)
637 defer cancel()
638
639 ctx, cancel = context.WithTimeout(ctx, handleDownstreamMessageTimeout)
640 defer cancel()
641
642 switch msg.Command {
643 case "QUIT":
644 return dc.Close()
645 default:
646 if dc.registered {
647 return dc.handleMessageRegistered(ctx, msg)
648 } else {
649 return dc.handleMessageUnregistered(ctx, msg)
650 }
651 }
652}
653
654func (dc *downstreamConn) handleMessageUnregistered(ctx context.Context, msg *irc.Message) error {
655 switch msg.Command {
656 case "NICK":
657 var nick string
658 if err := parseMessageParams(msg, &nick); err != nil {
659 return err
660 }
661 if nick == "" || strings.ContainsAny(nick, illegalNickChars) {
662 return ircError{&irc.Message{
663 Command: irc.ERR_ERRONEUSNICKNAME,
664 Params: []string{dc.nick, nick, "contains illegal characters"},
665 }}
666 }
667 nickCM := casemapASCII(nick)
668 if nickCM == serviceNickCM {
669 return ircError{&irc.Message{
670 Command: irc.ERR_NICKNAMEINUSE,
671 Params: []string{dc.nick, nick, "Nickname reserved for bouncer service"},
672 }}
673 }
674 dc.nick = nick
675 dc.nickCM = nickCM
676 case "USER":
677 if err := parseMessageParams(msg, &dc.rawUsername, nil, nil, &dc.realname); err != nil {
678 return err
679 }
680 case "PASS":
681 if err := parseMessageParams(msg, &dc.password); err != nil {
682 return err
683 }
684 case "CAP":
685 var subCmd string
686 if err := parseMessageParams(msg, &subCmd); err != nil {
687 return err
688 }
689 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
690 return err
691 }
692 case "AUTHENTICATE":
693 credentials, err := dc.handleAuthenticateCommand(msg)
694 if err != nil {
695 return err
696 } else if credentials == nil {
697 break
698 }
699
700 if err := dc.authenticate(ctx, credentials.plainUsername, credentials.plainPassword); err != nil {
701 dc.logger.Printf("SASL authentication error: %v", err)
702 dc.endSASL(&irc.Message{
703 Prefix: dc.srv.prefix(),
704 Command: irc.ERR_SASLFAIL,
705 Params: []string{"Authentication failed"},
706 })
707 break
708 }
709
710 // Technically we should send RPL_LOGGEDIN here. However we use
711 // RPL_LOGGEDIN to mirror the upstream connection status. Let's
712 // see how many clients that breaks. See:
713 // https://github.com/ircv3/ircv3-specifications/pull/476
714 dc.endSASL(nil)
715 case "BOUNCER":
716 var subcommand string
717 if err := parseMessageParams(msg, &subcommand); err != nil {
718 return err
719 }
720
721 switch strings.ToUpper(subcommand) {
722 case "BIND":
723 var idStr string
724 if err := parseMessageParams(msg, nil, &idStr); err != nil {
725 return err
726 }
727
728 if dc.user == nil {
729 return ircError{&irc.Message{
730 Command: "FAIL",
731 Params: []string{"BOUNCER", "ACCOUNT_REQUIRED", "BIND", "Authentication needed to bind to bouncer network"},
732 }}
733 }
734
735 id, err := parseBouncerNetID(subcommand, idStr)
736 if err != nil {
737 return err
738 }
739
740 var match *network
741 dc.user.forEachNetwork(func(net *network) {
742 if net.ID == id {
743 match = net
744 }
745 })
746 if match == nil {
747 return ircError{&irc.Message{
748 Command: "FAIL",
749 Params: []string{"BOUNCER", "INVALID_NETID", idStr, "Unknown network ID"},
750 }}
751 }
752
753 dc.networkName = match.GetName()
754 }
755 default:
756 dc.logger.Printf("unhandled message: %v", msg)
757 return newUnknownCommandError(msg.Command)
758 }
759 if dc.rawUsername != "" && dc.nick != "*" && !dc.negotiatingCaps {
760 return dc.register(ctx)
761 }
762 return nil
763}
764
765func (dc *downstreamConn) handleCapCommand(cmd string, args []string) error {
766 cmd = strings.ToUpper(cmd)
767
768 switch cmd {
769 case "LS":
770 if len(args) > 0 {
771 var err error
772 if dc.capVersion, err = strconv.Atoi(args[0]); err != nil {
773 return err
774 }
775 }
776 if !dc.registered && dc.capVersion >= 302 {
777 // Let downstream show everything it supports, and trim
778 // down the available capabilities when upstreams are
779 // known.
780 for k, v := range needAllDownstreamCaps {
781 dc.supportedCaps[k] = v
782 }
783 }
784
785 caps := make([]string, 0, len(dc.supportedCaps))
786 for k, v := range dc.supportedCaps {
787 if dc.capVersion >= 302 && v != "" {
788 caps = append(caps, k+"="+v)
789 } else {
790 caps = append(caps, k)
791 }
792 }
793
794 // TODO: multi-line replies
795 dc.SendMessage(&irc.Message{
796 Prefix: dc.srv.prefix(),
797 Command: "CAP",
798 Params: []string{dc.nick, "LS", strings.Join(caps, " ")},
799 })
800
801 if dc.capVersion >= 302 {
802 // CAP version 302 implicitly enables cap-notify
803 dc.caps["cap-notify"] = true
804 }
805
806 if !dc.registered {
807 dc.negotiatingCaps = true
808 }
809 case "LIST":
810 var caps []string
811 for name, enabled := range dc.caps {
812 if enabled {
813 caps = append(caps, name)
814 }
815 }
816
817 // TODO: multi-line replies
818 dc.SendMessage(&irc.Message{
819 Prefix: dc.srv.prefix(),
820 Command: "CAP",
821 Params: []string{dc.nick, "LIST", strings.Join(caps, " ")},
822 })
823 case "REQ":
824 if len(args) == 0 {
825 return ircError{&irc.Message{
826 Command: err_invalidcapcmd,
827 Params: []string{dc.nick, cmd, "Missing argument in CAP REQ command"},
828 }}
829 }
830
831 // TODO: atomically ack/nak the whole capability set
832 caps := strings.Fields(args[0])
833 ack := true
834 for _, name := range caps {
835 name = strings.ToLower(name)
836 enable := !strings.HasPrefix(name, "-")
837 if !enable {
838 name = strings.TrimPrefix(name, "-")
839 }
840
841 if enable == dc.caps[name] {
842 continue
843 }
844
845 _, ok := dc.supportedCaps[name]
846 if !ok {
847 ack = false
848 break
849 }
850
851 if name == "cap-notify" && dc.capVersion >= 302 && !enable {
852 // cap-notify cannot be disabled with CAP version 302
853 ack = false
854 break
855 }
856
857 dc.caps[name] = enable
858 }
859
860 reply := "NAK"
861 if ack {
862 reply = "ACK"
863 }
864 dc.SendMessage(&irc.Message{
865 Prefix: dc.srv.prefix(),
866 Command: "CAP",
867 Params: []string{dc.nick, reply, args[0]},
868 })
869
870 if !dc.registered {
871 dc.negotiatingCaps = true
872 }
873 case "END":
874 dc.negotiatingCaps = false
875 default:
876 return ircError{&irc.Message{
877 Command: err_invalidcapcmd,
878 Params: []string{dc.nick, cmd, "Unknown CAP command"},
879 }}
880 }
881 return nil
882}
883
884func (dc *downstreamConn) handleAuthenticateCommand(msg *irc.Message) (result *downstreamSASL, err error) {
885 defer func() {
886 if err != nil {
887 dc.sasl = nil
888 }
889 }()
890
891 if !dc.caps["sasl"] {
892 return nil, ircError{&irc.Message{
893 Prefix: dc.srv.prefix(),
894 Command: irc.ERR_SASLFAIL,
895 Params: []string{"*", "AUTHENTICATE requires the \"sasl\" capability to be enabled"},
896 }}
897 }
898 if len(msg.Params) == 0 {
899 return nil, ircError{&irc.Message{
900 Prefix: dc.srv.prefix(),
901 Command: irc.ERR_SASLFAIL,
902 Params: []string{"*", "Missing AUTHENTICATE argument"},
903 }}
904 }
905 if msg.Params[0] == "*" {
906 return nil, ircError{&irc.Message{
907 Prefix: dc.srv.prefix(),
908 Command: irc.ERR_SASLABORTED,
909 Params: []string{"*", "SASL authentication aborted"},
910 }}
911 }
912
913 var resp []byte
914 if dc.sasl == nil {
915 mech := strings.ToUpper(msg.Params[0])
916 var server sasl.Server
917 switch mech {
918 case "PLAIN":
919 server = sasl.NewPlainServer(sasl.PlainAuthenticator(func(identity, username, password string) error {
920 dc.sasl.plainUsername = username
921 dc.sasl.plainPassword = password
922 return nil
923 }))
924 default:
925 return nil, ircError{&irc.Message{
926 Prefix: dc.srv.prefix(),
927 Command: irc.ERR_SASLFAIL,
928 Params: []string{"*", fmt.Sprintf("Unsupported SASL mechanism %q", mech)},
929 }}
930 }
931
932 dc.sasl = &downstreamSASL{server: server}
933 } else {
934 // TODO: multi-line messages
935 if msg.Params[0] == "+" {
936 resp = nil
937 } else if resp, err = base64.StdEncoding.DecodeString(msg.Params[0]); err != nil {
938 return nil, ircError{&irc.Message{
939 Prefix: dc.srv.prefix(),
940 Command: irc.ERR_SASLFAIL,
941 Params: []string{"*", "Invalid base64-encoded response"},
942 }}
943 }
944 }
945
946 challenge, done, err := dc.sasl.server.Next(resp)
947 if err != nil {
948 return nil, err
949 } else if done {
950 return dc.sasl, nil
951 } else {
952 challengeStr := "+"
953 if len(challenge) > 0 {
954 challengeStr = base64.StdEncoding.EncodeToString(challenge)
955 }
956
957 // TODO: multi-line messages
958 dc.SendMessage(&irc.Message{
959 Prefix: dc.srv.prefix(),
960 Command: "AUTHENTICATE",
961 Params: []string{challengeStr},
962 })
963 return nil, nil
964 }
965}
966
967func (dc *downstreamConn) endSASL(msg *irc.Message) {
968 if dc.sasl == nil {
969 return
970 }
971
972 dc.sasl = nil
973
974 if msg != nil {
975 dc.SendMessage(msg)
976 } else {
977 dc.SendMessage(&irc.Message{
978 Prefix: dc.srv.prefix(),
979 Command: irc.RPL_SASLSUCCESS,
980 Params: []string{dc.nick, "SASL authentication successful"},
981 })
982 }
983}
984
985func (dc *downstreamConn) setSupportedCap(name, value string) {
986 prevValue, hasPrev := dc.supportedCaps[name]
987 changed := !hasPrev || prevValue != value
988 dc.supportedCaps[name] = value
989
990 if !dc.caps["cap-notify"] || !changed {
991 return
992 }
993
994 cap := name
995 if value != "" && dc.capVersion >= 302 {
996 cap = name + "=" + value
997 }
998
999 dc.SendMessage(&irc.Message{
1000 Prefix: dc.srv.prefix(),
1001 Command: "CAP",
1002 Params: []string{dc.nick, "NEW", cap},
1003 })
1004}
1005
1006func (dc *downstreamConn) unsetSupportedCap(name string) {
1007 _, hasPrev := dc.supportedCaps[name]
1008 delete(dc.supportedCaps, name)
1009 delete(dc.caps, name)
1010
1011 if !dc.caps["cap-notify"] || !hasPrev {
1012 return
1013 }
1014
1015 dc.SendMessage(&irc.Message{
1016 Prefix: dc.srv.prefix(),
1017 Command: "CAP",
1018 Params: []string{dc.nick, "DEL", name},
1019 })
1020}
1021
1022func (dc *downstreamConn) updateSupportedCaps() {
1023 supportedCaps := make(map[string]bool)
1024 for cap := range needAllDownstreamCaps {
1025 supportedCaps[cap] = true
1026 }
1027 dc.forEachUpstream(func(uc *upstreamConn) {
1028 for cap, supported := range supportedCaps {
1029 supportedCaps[cap] = supported && uc.caps[cap]
1030 }
1031 })
1032
1033 for cap, supported := range supportedCaps {
1034 if supported {
1035 dc.setSupportedCap(cap, needAllDownstreamCaps[cap])
1036 } else {
1037 dc.unsetSupportedCap(cap)
1038 }
1039 }
1040
1041 if uc := dc.upstream(); uc != nil && uc.supportsSASL("PLAIN") {
1042 dc.setSupportedCap("sasl", "PLAIN")
1043 } else if dc.network != nil {
1044 dc.unsetSupportedCap("sasl")
1045 }
1046
1047 if _, ok := dc.user.msgStore.(chatHistoryMessageStore); ok && dc.network != nil {
1048 dc.setSupportedCap("draft/event-playback", "")
1049 } else {
1050 dc.unsetSupportedCap("draft/event-playback")
1051 }
1052}
1053
1054func (dc *downstreamConn) updateNick() {
1055 if uc := dc.upstream(); uc != nil && uc.nick != dc.nick {
1056 dc.SendMessage(&irc.Message{
1057 Prefix: dc.prefix(),
1058 Command: "NICK",
1059 Params: []string{uc.nick},
1060 })
1061 dc.nick = uc.nick
1062 dc.nickCM = casemapASCII(dc.nick)
1063 }
1064}
1065
1066func (dc *downstreamConn) updateRealname() {
1067 if uc := dc.upstream(); uc != nil && uc.realname != dc.realname && dc.caps["setname"] {
1068 dc.SendMessage(&irc.Message{
1069 Prefix: dc.prefix(),
1070 Command: "SETNAME",
1071 Params: []string{uc.realname},
1072 })
1073 dc.realname = uc.realname
1074 }
1075}
1076
1077func (dc *downstreamConn) updateAccount() {
1078 var account string
1079 if dc.network == nil {
1080 account = dc.user.Username
1081 } else if uc := dc.upstream(); uc != nil {
1082 account = uc.account
1083 } else {
1084 return
1085 }
1086
1087 if dc.account == account || !dc.caps["sasl"] {
1088 return
1089 }
1090
1091 if account != "" {
1092 dc.SendMessage(&irc.Message{
1093 Prefix: dc.srv.prefix(),
1094 Command: irc.RPL_LOGGEDIN,
1095 Params: []string{dc.nick, dc.prefix().String(), account, "You are logged in as " + account},
1096 })
1097 } else {
1098 dc.SendMessage(&irc.Message{
1099 Prefix: dc.srv.prefix(),
1100 Command: irc.RPL_LOGGEDOUT,
1101 Params: []string{dc.nick, dc.prefix().String(), "You are logged out"},
1102 })
1103 }
1104
1105 dc.account = account
1106}
1107
1108func sanityCheckServer(ctx context.Context, addr string) error {
1109 ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
1110 defer cancel()
1111
1112 conn, err := new(tls.Dialer).DialContext(ctx, "tcp", addr)
1113 if err != nil {
1114 return err
1115 }
1116
1117 return conn.Close()
1118}
1119
1120func unmarshalUsername(rawUsername string) (username, client, network string) {
1121 username = rawUsername
1122
1123 i := strings.IndexAny(username, "/@")
1124 j := strings.LastIndexAny(username, "/@")
1125 if i >= 0 {
1126 username = rawUsername[:i]
1127 }
1128 if j >= 0 {
1129 if rawUsername[j] == '@' {
1130 client = rawUsername[j+1:]
1131 } else {
1132 network = rawUsername[j+1:]
1133 }
1134 }
1135 if i >= 0 && j >= 0 && i < j {
1136 if rawUsername[i] == '@' {
1137 client = rawUsername[i+1 : j]
1138 } else {
1139 network = rawUsername[i+1 : j]
1140 }
1141 }
1142
1143 return username, client, network
1144}
1145
1146func (dc *downstreamConn) authenticate(ctx context.Context, username, password string) error {
1147 username, clientName, networkName := unmarshalUsername(username)
1148
1149 u, err := dc.srv.db.GetUser(ctx, username)
1150 if err != nil {
1151 dc.logger.Printf("failed authentication for %q: user not found: %v", username, err)
1152 return errAuthFailed
1153 }
1154
1155 // Password auth disabled
1156 if u.Password == "" {
1157 return errAuthFailed
1158 }
1159
1160 err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
1161 if err != nil {
1162 dc.logger.Printf("failed authentication for %q: wrong password: %v", username, err)
1163 return errAuthFailed
1164 }
1165
1166 dc.user = dc.srv.getUser(username)
1167 if dc.user == nil {
1168 dc.logger.Printf("failed authentication for %q: user not active", username)
1169 return errAuthFailed
1170 }
1171 dc.clientName = clientName
1172 dc.networkName = networkName
1173 return nil
1174}
1175
1176func (dc *downstreamConn) register(ctx context.Context) error {
1177 if dc.registered {
1178 return fmt.Errorf("tried to register twice")
1179 }
1180
1181 if dc.sasl != nil {
1182 dc.endSASL(&irc.Message{
1183 Prefix: dc.srv.prefix(),
1184 Command: irc.ERR_SASLABORTED,
1185 Params: []string{"*", "SASL authentication aborted"},
1186 })
1187 }
1188
1189 password := dc.password
1190 dc.password = ""
1191 if dc.user == nil {
1192 if err := dc.authenticate(ctx, dc.rawUsername, password); err != nil {
1193 return err
1194 }
1195 }
1196
1197 if dc.clientName == "" && dc.networkName == "" {
1198 _, dc.clientName, dc.networkName = unmarshalUsername(dc.rawUsername)
1199 }
1200
1201 dc.registered = true
1202 dc.logger.Printf("registration complete for user %q", dc.user.Username)
1203 return nil
1204}
1205
1206func (dc *downstreamConn) loadNetwork(ctx context.Context) error {
1207 if dc.networkName == "" {
1208 return nil
1209 }
1210
1211 network := dc.user.getNetwork(dc.networkName)
1212 if network == nil {
1213 addr := dc.networkName
1214 if !strings.ContainsRune(addr, ':') {
1215 addr = addr + ":6697"
1216 }
1217
1218 dc.logger.Printf("trying to connect to new network %q", addr)
1219 if err := sanityCheckServer(ctx, addr); err != nil {
1220 dc.logger.Printf("failed to connect to %q: %v", addr, err)
1221 return ircError{&irc.Message{
1222 Command: irc.ERR_PASSWDMISMATCH,
1223 Params: []string{"*", fmt.Sprintf("Failed to connect to %q", dc.networkName)},
1224 }}
1225 }
1226
1227 // Some clients only allow specifying the nickname (and use the
1228 // nickname as a username too). Strip the network name from the
1229 // nickname when auto-saving networks.
1230 nick, _, _ := unmarshalUsername(dc.nick)
1231
1232 dc.logger.Printf("auto-saving network %q", dc.networkName)
1233 var err error
1234 network, err = dc.user.createNetwork(ctx, &Network{
1235 Addr: dc.networkName,
1236 Nick: nick,
1237 Enabled: true,
1238 })
1239 if err != nil {
1240 return err
1241 }
1242 }
1243
1244 dc.network = network
1245 return nil
1246}
1247
1248func (dc *downstreamConn) welcome(ctx context.Context) error {
1249 if dc.user == nil || !dc.registered {
1250 panic("tried to welcome an unregistered connection")
1251 }
1252
1253 // TODO: doing this might take some time. We should do it in dc.register
1254 // instead, but we'll potentially be adding a new network and this must be
1255 // done in the user goroutine.
1256 if err := dc.loadNetwork(ctx); err != nil {
1257 return err
1258 }
1259
1260 if dc.network == nil && !dc.caps["soju.im/bouncer-networks"] && dc.srv.Config().MultiUpstream {
1261 dc.isMultiUpstream = true
1262 }
1263
1264 dc.updateSupportedCaps()
1265
1266 isupport := []string{
1267 fmt.Sprintf("CHATHISTORY=%v", chatHistoryLimit),
1268 "CASEMAPPING=ascii",
1269 }
1270
1271 if dc.network != nil {
1272 isupport = append(isupport, fmt.Sprintf("BOUNCER_NETID=%v", dc.network.ID))
1273 }
1274 if title := dc.srv.Config().Title; dc.network == nil && title != "" {
1275 isupport = append(isupport, "NETWORK="+encodeISUPPORT(title))
1276 }
1277 if dc.network == nil && !dc.isMultiUpstream {
1278 isupport = append(isupport, "WHOX")
1279 }
1280
1281 if uc := dc.upstream(); uc != nil {
1282 for k := range passthroughIsupport {
1283 v, ok := uc.isupport[k]
1284 if !ok {
1285 continue
1286 }
1287 if v != nil {
1288 isupport = append(isupport, fmt.Sprintf("%v=%v", k, *v))
1289 } else {
1290 isupport = append(isupport, k)
1291 }
1292 }
1293 }
1294
1295 dc.SendMessage(&irc.Message{
1296 Prefix: dc.srv.prefix(),
1297 Command: irc.RPL_WELCOME,
1298 Params: []string{dc.nick, "Welcome to soju, " + dc.nick},
1299 })
1300 dc.SendMessage(&irc.Message{
1301 Prefix: dc.srv.prefix(),
1302 Command: irc.RPL_YOURHOST,
1303 Params: []string{dc.nick, "Your host is " + dc.srv.Config().Hostname},
1304 })
1305 dc.SendMessage(&irc.Message{
1306 Prefix: dc.srv.prefix(),
1307 Command: irc.RPL_MYINFO,
1308 Params: []string{dc.nick, dc.srv.Config().Hostname, "soju", "aiwroO", "OovaimnqpsrtklbeI"},
1309 })
1310 for _, msg := range generateIsupport(dc.srv.prefix(), dc.nick, isupport) {
1311 dc.SendMessage(msg)
1312 }
1313 if uc := dc.upstream(); uc != nil {
1314 dc.SendMessage(&irc.Message{
1315 Prefix: dc.srv.prefix(),
1316 Command: irc.RPL_UMODEIS,
1317 Params: []string{dc.nick, "+" + string(uc.modes)},
1318 })
1319 }
1320 if dc.network == nil && !dc.isMultiUpstream && dc.user.Admin {
1321 dc.SendMessage(&irc.Message{
1322 Prefix: dc.srv.prefix(),
1323 Command: irc.RPL_UMODEIS,
1324 Params: []string{dc.nick, "+o"},
1325 })
1326 }
1327
1328 dc.updateNick()
1329 dc.updateRealname()
1330 dc.updateAccount()
1331
1332 if motd := dc.user.srv.Config().MOTD; motd != "" && dc.network == nil {
1333 for _, msg := range generateMOTD(dc.srv.prefix(), dc.nick, motd) {
1334 dc.SendMessage(msg)
1335 }
1336 } else {
1337 motdHint := "No MOTD"
1338 if dc.network != nil {
1339 motdHint = "Use /motd to read the message of the day"
1340 }
1341 dc.SendMessage(&irc.Message{
1342 Prefix: dc.srv.prefix(),
1343 Command: irc.ERR_NOMOTD,
1344 Params: []string{dc.nick, motdHint},
1345 })
1346 }
1347
1348 if dc.caps["soju.im/bouncer-networks-notify"] {
1349 dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
1350 dc.user.forEachNetwork(func(network *network) {
1351 idStr := fmt.Sprintf("%v", network.ID)
1352 attrs := getNetworkAttrs(network)
1353 dc.SendMessage(&irc.Message{
1354 Tags: irc.Tags{"batch": batchRef},
1355 Prefix: dc.srv.prefix(),
1356 Command: "BOUNCER",
1357 Params: []string{"NETWORK", idStr, attrs.String()},
1358 })
1359 })
1360 })
1361 }
1362
1363 dc.forEachUpstream(func(uc *upstreamConn) {
1364 for _, entry := range uc.channels.innerMap {
1365 ch := entry.value.(*upstreamChannel)
1366 if !ch.complete {
1367 continue
1368 }
1369 record := uc.network.channels.Value(ch.Name)
1370 if record != nil && record.Detached {
1371 continue
1372 }
1373
1374 dc.SendMessage(&irc.Message{
1375 Prefix: dc.prefix(),
1376 Command: "JOIN",
1377 Params: []string{dc.marshalEntity(ch.conn.network, ch.Name)},
1378 })
1379
1380 forwardChannel(dc, ch)
1381 }
1382 })
1383
1384 dc.forEachNetwork(func(net *network) {
1385 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
1386 return
1387 }
1388
1389 // Only send history if we're the first connected client with that name
1390 // for the network
1391 firstClient := true
1392 dc.user.forEachDownstream(func(c *downstreamConn) {
1393 if c != dc && c.clientName == dc.clientName && c.network == dc.network {
1394 firstClient = false
1395 }
1396 })
1397 if firstClient {
1398 net.delivered.ForEachTarget(func(target string) {
1399 lastDelivered := net.delivered.LoadID(target, dc.clientName)
1400 if lastDelivered == "" {
1401 return
1402 }
1403
1404 dc.sendTargetBacklog(ctx, net, target, lastDelivered)
1405
1406 // Fast-forward history to last message
1407 targetCM := net.casemap(target)
1408 lastID, err := dc.user.msgStore.LastMsgID(&net.Network, targetCM, time.Now())
1409 if err != nil {
1410 dc.logger.Printf("failed to get last message ID: %v", err)
1411 return
1412 }
1413 net.delivered.StoreID(target, dc.clientName, lastID)
1414 })
1415 }
1416 })
1417
1418 return nil
1419}
1420
1421// messageSupportsBacklog checks whether the provided message can be sent as
1422// part of an history batch.
1423func (dc *downstreamConn) messageSupportsBacklog(msg *irc.Message) bool {
1424 // Don't replay all messages, because that would mess up client
1425 // state. For instance we just sent the list of users, sending
1426 // PART messages for one of these users would be incorrect.
1427 switch msg.Command {
1428 case "PRIVMSG", "NOTICE":
1429 return true
1430 }
1431 return false
1432}
1433
1434func (dc *downstreamConn) sendTargetBacklog(ctx context.Context, net *network, target, msgID string) {
1435 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
1436 return
1437 }
1438
1439 ch := net.channels.Value(target)
1440
1441 ctx, cancel := context.WithTimeout(ctx, backlogTimeout)
1442 defer cancel()
1443
1444 targetCM := net.casemap(target)
1445 history, err := dc.user.msgStore.LoadLatestID(ctx, &net.Network, targetCM, msgID, backlogLimit)
1446 if err != nil {
1447 dc.logger.Printf("failed to send backlog for %q: %v", target, err)
1448 return
1449 }
1450
1451 dc.SendBatch("chathistory", []string{dc.marshalEntity(net, target)}, nil, func(batchRef irc.TagValue) {
1452 for _, msg := range history {
1453 if ch != nil && ch.Detached {
1454 if net.detachedMessageNeedsRelay(ch, msg) {
1455 dc.relayDetachedMessage(net, msg)
1456 }
1457 } else {
1458 msg.Tags["batch"] = batchRef
1459 dc.SendMessage(dc.marshalMessage(msg, net))
1460 }
1461 }
1462 })
1463}
1464
1465func (dc *downstreamConn) relayDetachedMessage(net *network, msg *irc.Message) {
1466 if msg.Command != "PRIVMSG" && msg.Command != "NOTICE" {
1467 return
1468 }
1469
1470 sender := msg.Prefix.Name
1471 target, text := msg.Params[0], msg.Params[1]
1472 if net.isHighlight(msg) {
1473 sendServiceNOTICE(dc, fmt.Sprintf("highlight in %v: <%v> %v", dc.marshalEntity(net, target), sender, text))
1474 } else {
1475 sendServiceNOTICE(dc, fmt.Sprintf("message in %v: <%v> %v", dc.marshalEntity(net, target), sender, text))
1476 }
1477}
1478
1479func (dc *downstreamConn) runUntilRegistered() error {
1480 ctx, cancel := context.WithTimeout(context.TODO(), downstreamRegisterTimeout)
1481 defer cancel()
1482
1483 // Close the connection with an error if the deadline is exceeded
1484 go func() {
1485 <-ctx.Done()
1486 if err := ctx.Err(); err == context.DeadlineExceeded {
1487 dc.SendMessage(&irc.Message{
1488 Prefix: dc.srv.prefix(),
1489 Command: "ERROR",
1490 Params: []string{"Connection registration timed out"},
1491 })
1492 dc.Close()
1493 }
1494 }()
1495
1496 for !dc.registered {
1497 msg, err := dc.ReadMessage()
1498 if err != nil {
1499 return fmt.Errorf("failed to read IRC command: %w", err)
1500 }
1501
1502 err = dc.handleMessage(ctx, msg)
1503 if ircErr, ok := err.(ircError); ok {
1504 ircErr.Message.Prefix = dc.srv.prefix()
1505 dc.SendMessage(ircErr.Message)
1506 } else if err != nil {
1507 return fmt.Errorf("failed to handle IRC command %q: %v", msg, err)
1508 }
1509 }
1510
1511 return nil
1512}
1513
1514func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.Message) error {
1515 switch msg.Command {
1516 case "CAP":
1517 var subCmd string
1518 if err := parseMessageParams(msg, &subCmd); err != nil {
1519 return err
1520 }
1521 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
1522 return err
1523 }
1524 case "PING":
1525 var source, destination string
1526 if err := parseMessageParams(msg, &source); err != nil {
1527 return err
1528 }
1529 if len(msg.Params) > 1 {
1530 destination = msg.Params[1]
1531 }
1532 hostname := dc.srv.Config().Hostname
1533 if destination != "" && destination != hostname {
1534 return ircError{&irc.Message{
1535 Command: irc.ERR_NOSUCHSERVER,
1536 Params: []string{dc.nick, destination, "No such server"},
1537 }}
1538 }
1539 dc.SendMessage(&irc.Message{
1540 Prefix: dc.srv.prefix(),
1541 Command: "PONG",
1542 Params: []string{hostname, source},
1543 })
1544 return nil
1545 case "PONG":
1546 if len(msg.Params) == 0 {
1547 return newNeedMoreParamsError(msg.Command)
1548 }
1549 token := msg.Params[len(msg.Params)-1]
1550 dc.handlePong(token)
1551 case "USER":
1552 return ircError{&irc.Message{
1553 Command: irc.ERR_ALREADYREGISTERED,
1554 Params: []string{dc.nick, "You may not reregister"},
1555 }}
1556 case "NICK":
1557 var rawNick string
1558 if err := parseMessageParams(msg, &rawNick); err != nil {
1559 return err
1560 }
1561
1562 nick := rawNick
1563 var upstream *upstreamConn
1564 if dc.upstream() == nil {
1565 uc, unmarshaledNick, err := dc.unmarshalEntity(nick)
1566 if err == nil { // NICK nick/network: NICK only on a specific upstream
1567 upstream = uc
1568 nick = unmarshaledNick
1569 }
1570 }
1571
1572 if nick == "" || strings.ContainsAny(nick, illegalNickChars) {
1573 return ircError{&irc.Message{
1574 Command: irc.ERR_ERRONEUSNICKNAME,
1575 Params: []string{dc.nick, rawNick, "contains illegal characters"},
1576 }}
1577 }
1578 if casemapASCII(nick) == serviceNickCM {
1579 return ircError{&irc.Message{
1580 Command: irc.ERR_NICKNAMEINUSE,
1581 Params: []string{dc.nick, rawNick, "Nickname reserved for bouncer service"},
1582 }}
1583 }
1584
1585 var err error
1586 dc.forEachNetwork(func(n *network) {
1587 if err != nil || (upstream != nil && upstream.network != n) {
1588 return
1589 }
1590 n.Nick = nick
1591 err = dc.srv.db.StoreNetwork(ctx, dc.user.ID, &n.Network)
1592 })
1593 if err != nil {
1594 return err
1595 }
1596
1597 dc.forEachUpstream(func(uc *upstreamConn) {
1598 if upstream != nil && upstream != uc {
1599 return
1600 }
1601 uc.SendMessageLabeled(dc.id, &irc.Message{
1602 Command: "NICK",
1603 Params: []string{nick},
1604 })
1605 })
1606
1607 if dc.upstream() == nil && upstream == nil && dc.nick != nick {
1608 dc.SendMessage(&irc.Message{
1609 Prefix: dc.prefix(),
1610 Command: "NICK",
1611 Params: []string{nick},
1612 })
1613 dc.nick = nick
1614 dc.nickCM = casemapASCII(dc.nick)
1615 }
1616 case "SETNAME":
1617 var realname string
1618 if err := parseMessageParams(msg, &realname); err != nil {
1619 return err
1620 }
1621
1622 // If the client just resets to the default, just wipe the per-network
1623 // preference
1624 storeRealname := realname
1625 if realname == dc.user.Realname {
1626 storeRealname = ""
1627 }
1628
1629 var storeErr error
1630 var needUpdate []Network
1631 dc.forEachNetwork(func(n *network) {
1632 // We only need to call updateNetwork for upstreams that don't
1633 // support setname
1634 if uc := n.conn; uc != nil && uc.caps["setname"] {
1635 uc.SendMessageLabeled(dc.id, &irc.Message{
1636 Command: "SETNAME",
1637 Params: []string{realname},
1638 })
1639
1640 n.Realname = storeRealname
1641 if err := dc.srv.db.StoreNetwork(ctx, dc.user.ID, &n.Network); err != nil {
1642 dc.logger.Printf("failed to store network realname: %v", err)
1643 storeErr = err
1644 }
1645 return
1646 }
1647
1648 record := n.Network // copy network record because we'll mutate it
1649 record.Realname = storeRealname
1650 needUpdate = append(needUpdate, record)
1651 })
1652
1653 // Walk the network list as a second step, because updateNetwork
1654 // mutates the original list
1655 for _, record := range needUpdate {
1656 if _, err := dc.user.updateNetwork(ctx, &record); err != nil {
1657 dc.logger.Printf("failed to update network realname: %v", err)
1658 storeErr = err
1659 }
1660 }
1661 if storeErr != nil {
1662 return ircError{&irc.Message{
1663 Command: "FAIL",
1664 Params: []string{"SETNAME", "CANNOT_CHANGE_REALNAME", "Failed to update realname"},
1665 }}
1666 }
1667
1668 if dc.upstream() == nil {
1669 dc.SendMessage(&irc.Message{
1670 Prefix: dc.prefix(),
1671 Command: "SETNAME",
1672 Params: []string{realname},
1673 })
1674 }
1675 case "JOIN":
1676 var namesStr string
1677 if err := parseMessageParams(msg, &namesStr); err != nil {
1678 return err
1679 }
1680
1681 var keys []string
1682 if len(msg.Params) > 1 {
1683 keys = strings.Split(msg.Params[1], ",")
1684 }
1685
1686 for i, name := range strings.Split(namesStr, ",") {
1687 uc, upstreamName, err := dc.unmarshalEntity(name)
1688 if err != nil {
1689 return err
1690 }
1691
1692 var key string
1693 if len(keys) > i {
1694 key = keys[i]
1695 }
1696
1697 if !uc.isChannel(upstreamName) {
1698 dc.SendMessage(&irc.Message{
1699 Prefix: dc.srv.prefix(),
1700 Command: irc.ERR_NOSUCHCHANNEL,
1701 Params: []string{name, "Not a channel name"},
1702 })
1703 continue
1704 }
1705
1706 params := []string{upstreamName}
1707 if key != "" {
1708 params = append(params, key)
1709 }
1710 uc.SendMessageLabeled(dc.id, &irc.Message{
1711 Command: "JOIN",
1712 Params: params,
1713 })
1714
1715 ch := uc.network.channels.Value(upstreamName)
1716 if ch != nil {
1717 // Don't clear the channel key if there's one set
1718 // TODO: add a way to unset the channel key
1719 if key != "" {
1720 ch.Key = key
1721 }
1722 uc.network.attach(ch)
1723 } else {
1724 ch = &Channel{
1725 Name: upstreamName,
1726 Key: key,
1727 }
1728 uc.network.channels.SetValue(upstreamName, ch)
1729 }
1730 if err := dc.srv.db.StoreChannel(ctx, uc.network.ID, ch); err != nil {
1731 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1732 }
1733 }
1734 case "PART":
1735 var namesStr string
1736 if err := parseMessageParams(msg, &namesStr); err != nil {
1737 return err
1738 }
1739
1740 var reason string
1741 if len(msg.Params) > 1 {
1742 reason = msg.Params[1]
1743 }
1744
1745 for _, name := range strings.Split(namesStr, ",") {
1746 uc, upstreamName, err := dc.unmarshalEntity(name)
1747 if err != nil {
1748 return err
1749 }
1750
1751 if strings.EqualFold(reason, "detach") {
1752 ch := uc.network.channels.Value(upstreamName)
1753 if ch != nil {
1754 uc.network.detach(ch)
1755 } else {
1756 ch = &Channel{
1757 Name: name,
1758 Detached: true,
1759 }
1760 uc.network.channels.SetValue(upstreamName, ch)
1761 }
1762 if err := dc.srv.db.StoreChannel(ctx, uc.network.ID, ch); err != nil {
1763 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1764 }
1765 } else {
1766 params := []string{upstreamName}
1767 if reason != "" {
1768 params = append(params, reason)
1769 }
1770 uc.SendMessageLabeled(dc.id, &irc.Message{
1771 Command: "PART",
1772 Params: params,
1773 })
1774
1775 if err := uc.network.deleteChannel(ctx, upstreamName); err != nil {
1776 dc.logger.Printf("failed to delete channel %q: %v", upstreamName, err)
1777 }
1778 }
1779 }
1780 case "KICK":
1781 var channelStr, userStr string
1782 if err := parseMessageParams(msg, &channelStr, &userStr); err != nil {
1783 return err
1784 }
1785
1786 channels := strings.Split(channelStr, ",")
1787 users := strings.Split(userStr, ",")
1788
1789 var reason string
1790 if len(msg.Params) > 2 {
1791 reason = msg.Params[2]
1792 }
1793
1794 if len(channels) != 1 && len(channels) != len(users) {
1795 return ircError{&irc.Message{
1796 Command: irc.ERR_BADCHANMASK,
1797 Params: []string{dc.nick, channelStr, "Bad channel mask"},
1798 }}
1799 }
1800
1801 for i, user := range users {
1802 var channel string
1803 if len(channels) == 1 {
1804 channel = channels[0]
1805 } else {
1806 channel = channels[i]
1807 }
1808
1809 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1810 if err != nil {
1811 return err
1812 }
1813
1814 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1815 if err != nil {
1816 return err
1817 }
1818
1819 if ucChannel != ucUser {
1820 return ircError{&irc.Message{
1821 Command: irc.ERR_USERNOTINCHANNEL,
1822 Params: []string{dc.nick, user, channel, "They are on another network"},
1823 }}
1824 }
1825 uc := ucChannel
1826
1827 params := []string{upstreamChannel, upstreamUser}
1828 if reason != "" {
1829 params = append(params, reason)
1830 }
1831 uc.SendMessageLabeled(dc.id, &irc.Message{
1832 Command: "KICK",
1833 Params: params,
1834 })
1835 }
1836 case "MODE":
1837 var name string
1838 if err := parseMessageParams(msg, &name); err != nil {
1839 return err
1840 }
1841
1842 var modeStr string
1843 if len(msg.Params) > 1 {
1844 modeStr = msg.Params[1]
1845 }
1846
1847 if casemapASCII(name) == dc.nickCM {
1848 if modeStr != "" {
1849 if uc := dc.upstream(); uc != nil {
1850 uc.SendMessageLabeled(dc.id, &irc.Message{
1851 Command: "MODE",
1852 Params: []string{uc.nick, modeStr},
1853 })
1854 } else {
1855 dc.SendMessage(&irc.Message{
1856 Prefix: dc.srv.prefix(),
1857 Command: irc.ERR_UMODEUNKNOWNFLAG,
1858 Params: []string{dc.nick, "Cannot change user mode in multi-upstream mode"},
1859 })
1860 }
1861 } else {
1862 var userMode string
1863 if uc := dc.upstream(); uc != nil {
1864 userMode = string(uc.modes)
1865 }
1866
1867 dc.SendMessage(&irc.Message{
1868 Prefix: dc.srv.prefix(),
1869 Command: irc.RPL_UMODEIS,
1870 Params: []string{dc.nick, "+" + userMode},
1871 })
1872 }
1873 return nil
1874 }
1875
1876 uc, upstreamName, err := dc.unmarshalEntity(name)
1877 if err != nil {
1878 return err
1879 }
1880
1881 if !uc.isChannel(upstreamName) {
1882 return ircError{&irc.Message{
1883 Command: irc.ERR_USERSDONTMATCH,
1884 Params: []string{dc.nick, "Cannot change mode for other users"},
1885 }}
1886 }
1887
1888 if modeStr != "" {
1889 params := []string{upstreamName, modeStr}
1890 params = append(params, msg.Params[2:]...)
1891 uc.SendMessageLabeled(dc.id, &irc.Message{
1892 Command: "MODE",
1893 Params: params,
1894 })
1895 } else {
1896 ch := uc.channels.Value(upstreamName)
1897 if ch == nil {
1898 return ircError{&irc.Message{
1899 Command: irc.ERR_NOSUCHCHANNEL,
1900 Params: []string{dc.nick, name, "No such channel"},
1901 }}
1902 }
1903
1904 if ch.modes == nil {
1905 // we haven't received the initial RPL_CHANNELMODEIS yet
1906 // ignore the request, we will broadcast the modes later when we receive RPL_CHANNELMODEIS
1907 return nil
1908 }
1909
1910 modeStr, modeParams := ch.modes.Format()
1911 params := []string{dc.nick, name, modeStr}
1912 params = append(params, modeParams...)
1913
1914 dc.SendMessage(&irc.Message{
1915 Prefix: dc.srv.prefix(),
1916 Command: irc.RPL_CHANNELMODEIS,
1917 Params: params,
1918 })
1919 if ch.creationTime != "" {
1920 dc.SendMessage(&irc.Message{
1921 Prefix: dc.srv.prefix(),
1922 Command: rpl_creationtime,
1923 Params: []string{dc.nick, name, ch.creationTime},
1924 })
1925 }
1926 }
1927 case "TOPIC":
1928 var channel string
1929 if err := parseMessageParams(msg, &channel); err != nil {
1930 return err
1931 }
1932
1933 uc, upstreamName, err := dc.unmarshalEntity(channel)
1934 if err != nil {
1935 return err
1936 }
1937
1938 if len(msg.Params) > 1 { // setting topic
1939 topic := msg.Params[1]
1940 uc.SendMessageLabeled(dc.id, &irc.Message{
1941 Command: "TOPIC",
1942 Params: []string{upstreamName, topic},
1943 })
1944 } else { // getting topic
1945 ch := uc.channels.Value(upstreamName)
1946 if ch == nil {
1947 return ircError{&irc.Message{
1948 Command: irc.ERR_NOSUCHCHANNEL,
1949 Params: []string{dc.nick, upstreamName, "No such channel"},
1950 }}
1951 }
1952 sendTopic(dc, ch)
1953 }
1954 case "LIST":
1955 network := dc.network
1956 if network == nil && len(msg.Params) > 0 {
1957 var err error
1958 network, msg.Params[0], err = dc.unmarshalEntityNetwork(msg.Params[0])
1959 if err != nil {
1960 return err
1961 }
1962 }
1963 if network == nil {
1964 dc.SendMessage(&irc.Message{
1965 Prefix: dc.srv.prefix(),
1966 Command: irc.RPL_LISTEND,
1967 Params: []string{dc.nick, "LIST without a network suffix is not supported in multi-upstream mode"},
1968 })
1969 return nil
1970 }
1971
1972 uc := network.conn
1973 if uc == nil {
1974 dc.SendMessage(&irc.Message{
1975 Prefix: dc.srv.prefix(),
1976 Command: irc.RPL_LISTEND,
1977 Params: []string{dc.nick, "Disconnected from upstream server"},
1978 })
1979 return nil
1980 }
1981
1982 uc.enqueueCommand(dc, msg)
1983 case "NAMES":
1984 if len(msg.Params) == 0 {
1985 dc.SendMessage(&irc.Message{
1986 Prefix: dc.srv.prefix(),
1987 Command: irc.RPL_ENDOFNAMES,
1988 Params: []string{dc.nick, "*", "End of /NAMES list"},
1989 })
1990 return nil
1991 }
1992
1993 channels := strings.Split(msg.Params[0], ",")
1994 for _, channel := range channels {
1995 uc, upstreamName, err := dc.unmarshalEntity(channel)
1996 if err != nil {
1997 return err
1998 }
1999
2000 ch := uc.channels.Value(upstreamName)
2001 if ch != nil {
2002 sendNames(dc, ch)
2003 } else {
2004 // NAMES on a channel we have not joined, ask upstream
2005 uc.SendMessageLabeled(dc.id, &irc.Message{
2006 Command: "NAMES",
2007 Params: []string{upstreamName},
2008 })
2009 }
2010 }
2011 // For WHOX docs, see:
2012 // - http://faerion.sourceforge.net/doc/irc/whox.var
2013 // - https://github.com/quakenet/snircd/blob/master/doc/readme.who
2014 // Note, many features aren't widely implemented, such as flags and mask2
2015 case "WHO":
2016 if len(msg.Params) == 0 {
2017 // TODO: support WHO without parameters
2018 dc.SendMessage(&irc.Message{
2019 Prefix: dc.srv.prefix(),
2020 Command: irc.RPL_ENDOFWHO,
2021 Params: []string{dc.nick, "*", "End of /WHO list"},
2022 })
2023 return nil
2024 }
2025
2026 // Clients will use the first mask to match RPL_ENDOFWHO
2027 endOfWhoToken := msg.Params[0]
2028
2029 // TODO: add support for WHOX mask2
2030 mask := msg.Params[0]
2031 var options string
2032 if len(msg.Params) > 1 {
2033 options = msg.Params[1]
2034 }
2035
2036 optionsParts := strings.SplitN(options, "%", 2)
2037 // TODO: add support for WHOX flags in optionsParts[0]
2038 var fields, whoxToken string
2039 if len(optionsParts) == 2 {
2040 optionsParts := strings.SplitN(optionsParts[1], ",", 2)
2041 fields = strings.ToLower(optionsParts[0])
2042 if len(optionsParts) == 2 && strings.Contains(fields, "t") {
2043 whoxToken = optionsParts[1]
2044 }
2045 }
2046
2047 // TODO: support mixed bouncer/upstream WHO queries
2048 maskCM := casemapASCII(mask)
2049 if dc.network == nil && maskCM == dc.nickCM {
2050 // TODO: support AWAY (H/G) in self WHO reply
2051 flags := "H"
2052 if dc.user.Admin {
2053 flags += "*"
2054 }
2055 info := whoxInfo{
2056 Token: whoxToken,
2057 Username: dc.user.Username,
2058 Hostname: dc.hostname,
2059 Server: dc.srv.Config().Hostname,
2060 Nickname: dc.nick,
2061 Flags: flags,
2062 Account: dc.user.Username,
2063 Realname: dc.realname,
2064 }
2065 dc.SendMessage(generateWHOXReply(dc.srv.prefix(), dc.nick, fields, &info))
2066 dc.SendMessage(&irc.Message{
2067 Prefix: dc.srv.prefix(),
2068 Command: irc.RPL_ENDOFWHO,
2069 Params: []string{dc.nick, endOfWhoToken, "End of /WHO list"},
2070 })
2071 return nil
2072 }
2073 if maskCM == serviceNickCM {
2074 info := whoxInfo{
2075 Token: whoxToken,
2076 Username: servicePrefix.User,
2077 Hostname: servicePrefix.Host,
2078 Server: dc.srv.Config().Hostname,
2079 Nickname: serviceNick,
2080 Flags: "H*",
2081 Account: serviceNick,
2082 Realname: serviceRealname,
2083 }
2084 dc.SendMessage(generateWHOXReply(dc.srv.prefix(), dc.nick, fields, &info))
2085 dc.SendMessage(&irc.Message{
2086 Prefix: dc.srv.prefix(),
2087 Command: irc.RPL_ENDOFWHO,
2088 Params: []string{dc.nick, endOfWhoToken, "End of /WHO list"},
2089 })
2090 return nil
2091 }
2092
2093 // TODO: properly support WHO masks
2094 uc, upstreamMask, err := dc.unmarshalEntity(mask)
2095 if err != nil {
2096 return err
2097 }
2098
2099 params := []string{upstreamMask}
2100 if options != "" {
2101 params = append(params, options)
2102 }
2103
2104 uc.enqueueCommand(dc, &irc.Message{
2105 Command: "WHO",
2106 Params: params,
2107 })
2108 case "WHOIS":
2109 if len(msg.Params) == 0 {
2110 return ircError{&irc.Message{
2111 Command: irc.ERR_NONICKNAMEGIVEN,
2112 Params: []string{dc.nick, "No nickname given"},
2113 }}
2114 }
2115
2116 var target, mask string
2117 if len(msg.Params) == 1 {
2118 target = ""
2119 mask = msg.Params[0]
2120 } else {
2121 target = msg.Params[0]
2122 mask = msg.Params[1]
2123 }
2124 // TODO: support multiple WHOIS users
2125 if i := strings.IndexByte(mask, ','); i >= 0 {
2126 mask = mask[:i]
2127 }
2128
2129 if dc.network == nil && casemapASCII(mask) == dc.nickCM {
2130 dc.SendMessage(&irc.Message{
2131 Prefix: dc.srv.prefix(),
2132 Command: irc.RPL_WHOISUSER,
2133 Params: []string{dc.nick, dc.nick, dc.user.Username, dc.hostname, "*", dc.realname},
2134 })
2135 dc.SendMessage(&irc.Message{
2136 Prefix: dc.srv.prefix(),
2137 Command: irc.RPL_WHOISSERVER,
2138 Params: []string{dc.nick, dc.nick, dc.srv.Config().Hostname, "soju"},
2139 })
2140 if dc.user.Admin {
2141 dc.SendMessage(&irc.Message{
2142 Prefix: dc.srv.prefix(),
2143 Command: irc.RPL_WHOISOPERATOR,
2144 Params: []string{dc.nick, dc.nick, "is a bouncer administrator"},
2145 })
2146 }
2147 dc.SendMessage(&irc.Message{
2148 Prefix: dc.srv.prefix(),
2149 Command: rpl_whoisaccount,
2150 Params: []string{dc.nick, dc.nick, dc.user.Username, "is logged in as"},
2151 })
2152 dc.SendMessage(&irc.Message{
2153 Prefix: dc.srv.prefix(),
2154 Command: irc.RPL_ENDOFWHOIS,
2155 Params: []string{dc.nick, dc.nick, "End of /WHOIS list"},
2156 })
2157 return nil
2158 }
2159 if casemapASCII(mask) == serviceNickCM {
2160 dc.SendMessage(&irc.Message{
2161 Prefix: dc.srv.prefix(),
2162 Command: irc.RPL_WHOISUSER,
2163 Params: []string{dc.nick, serviceNick, servicePrefix.User, servicePrefix.Host, "*", serviceRealname},
2164 })
2165 dc.SendMessage(&irc.Message{
2166 Prefix: dc.srv.prefix(),
2167 Command: irc.RPL_WHOISSERVER,
2168 Params: []string{dc.nick, serviceNick, dc.srv.Config().Hostname, "soju"},
2169 })
2170 dc.SendMessage(&irc.Message{
2171 Prefix: dc.srv.prefix(),
2172 Command: irc.RPL_WHOISOPERATOR,
2173 Params: []string{dc.nick, serviceNick, "is the bouncer service"},
2174 })
2175 dc.SendMessage(&irc.Message{
2176 Prefix: dc.srv.prefix(),
2177 Command: rpl_whoisaccount,
2178 Params: []string{dc.nick, serviceNick, serviceNick, "is logged in as"},
2179 })
2180 dc.SendMessage(&irc.Message{
2181 Prefix: dc.srv.prefix(),
2182 Command: irc.RPL_ENDOFWHOIS,
2183 Params: []string{dc.nick, serviceNick, "End of /WHOIS list"},
2184 })
2185 return nil
2186 }
2187
2188 // TODO: support WHOIS masks
2189 uc, upstreamNick, err := dc.unmarshalEntity(mask)
2190 if err != nil {
2191 return err
2192 }
2193
2194 var params []string
2195 if target != "" {
2196 if target == mask { // WHOIS nick nick
2197 params = []string{upstreamNick, upstreamNick}
2198 } else {
2199 params = []string{target, upstreamNick}
2200 }
2201 } else {
2202 params = []string{upstreamNick}
2203 }
2204
2205 uc.SendMessageLabeled(dc.id, &irc.Message{
2206 Command: "WHOIS",
2207 Params: params,
2208 })
2209 case "PRIVMSG", "NOTICE":
2210 var targetsStr, text string
2211 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
2212 return err
2213 }
2214 tags := copyClientTags(msg.Tags)
2215
2216 for _, name := range strings.Split(targetsStr, ",") {
2217 if name == "$"+dc.srv.Config().Hostname || (name == "$*" && dc.network == nil) {
2218 // "$" means a server mask follows. If it's the bouncer's
2219 // hostname, broadcast the message to all bouncer users.
2220 if !dc.user.Admin {
2221 return ircError{&irc.Message{
2222 Prefix: dc.srv.prefix(),
2223 Command: irc.ERR_BADMASK,
2224 Params: []string{dc.nick, name, "Permission denied to broadcast message to all bouncer users"},
2225 }}
2226 }
2227
2228 dc.logger.Printf("broadcasting bouncer-wide %v: %v", msg.Command, text)
2229
2230 broadcastTags := tags.Copy()
2231 broadcastTags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
2232 broadcastMsg := &irc.Message{
2233 Tags: broadcastTags,
2234 Prefix: servicePrefix,
2235 Command: msg.Command,
2236 Params: []string{name, text},
2237 }
2238 dc.srv.forEachUser(func(u *user) {
2239 u.events <- eventBroadcast{broadcastMsg}
2240 })
2241 continue
2242 }
2243
2244 if dc.network == nil && casemapASCII(name) == dc.nickCM {
2245 dc.SendMessage(&irc.Message{
2246 Tags: msg.Tags.Copy(),
2247 Prefix: dc.prefix(),
2248 Command: msg.Command,
2249 Params: []string{name, text},
2250 })
2251 continue
2252 }
2253
2254 if msg.Command == "PRIVMSG" && casemapASCII(name) == serviceNickCM {
2255 if dc.caps["echo-message"] {
2256 echoTags := tags.Copy()
2257 echoTags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
2258 dc.SendMessage(&irc.Message{
2259 Tags: echoTags,
2260 Prefix: dc.prefix(),
2261 Command: msg.Command,
2262 Params: []string{name, text},
2263 })
2264 }
2265 handleServicePRIVMSG(ctx, dc, text)
2266 continue
2267 }
2268
2269 uc, upstreamName, err := dc.unmarshalEntity(name)
2270 if err != nil {
2271 return err
2272 }
2273
2274 if msg.Command == "PRIVMSG" && uc.network.casemap(upstreamName) == "nickserv" {
2275 dc.handleNickServPRIVMSG(ctx, uc, text)
2276 }
2277
2278 unmarshaledText := text
2279 if uc.isChannel(upstreamName) {
2280 unmarshaledText = dc.unmarshalText(uc, text)
2281 }
2282 uc.SendMessageLabeled(dc.id, &irc.Message{
2283 Tags: tags,
2284 Command: msg.Command,
2285 Params: []string{upstreamName, unmarshaledText},
2286 })
2287
2288 echoTags := tags.Copy()
2289 echoTags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
2290 if uc.account != "" {
2291 echoTags["account"] = irc.TagValue(uc.account)
2292 }
2293 echoMsg := &irc.Message{
2294 Tags: echoTags,
2295 Prefix: &irc.Prefix{Name: uc.nick},
2296 Command: msg.Command,
2297 Params: []string{upstreamName, text},
2298 }
2299 uc.produce(upstreamName, echoMsg, dc)
2300
2301 uc.updateChannelAutoDetach(upstreamName)
2302 }
2303 case "TAGMSG":
2304 var targetsStr string
2305 if err := parseMessageParams(msg, &targetsStr); err != nil {
2306 return err
2307 }
2308 tags := copyClientTags(msg.Tags)
2309
2310 for _, name := range strings.Split(targetsStr, ",") {
2311 if dc.network == nil && casemapASCII(name) == dc.nickCM {
2312 dc.SendMessage(&irc.Message{
2313 Tags: msg.Tags.Copy(),
2314 Prefix: dc.prefix(),
2315 Command: "TAGMSG",
2316 Params: []string{name},
2317 })
2318 continue
2319 }
2320
2321 if casemapASCII(name) == serviceNickCM {
2322 continue
2323 }
2324
2325 uc, upstreamName, err := dc.unmarshalEntity(name)
2326 if err != nil {
2327 return err
2328 }
2329 if _, ok := uc.caps["message-tags"]; !ok {
2330 continue
2331 }
2332
2333 uc.SendMessageLabeled(dc.id, &irc.Message{
2334 Tags: tags,
2335 Command: "TAGMSG",
2336 Params: []string{upstreamName},
2337 })
2338
2339 uc.updateChannelAutoDetach(upstreamName)
2340 }
2341 case "INVITE":
2342 var user, channel string
2343 if err := parseMessageParams(msg, &user, &channel); err != nil {
2344 return err
2345 }
2346
2347 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
2348 if err != nil {
2349 return err
2350 }
2351
2352 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
2353 if err != nil {
2354 return err
2355 }
2356
2357 if ucChannel != ucUser {
2358 return ircError{&irc.Message{
2359 Command: irc.ERR_USERNOTINCHANNEL,
2360 Params: []string{dc.nick, user, channel, "They are on another network"},
2361 }}
2362 }
2363 uc := ucChannel
2364
2365 uc.SendMessageLabeled(dc.id, &irc.Message{
2366 Command: "INVITE",
2367 Params: []string{upstreamUser, upstreamChannel},
2368 })
2369 case "AUTHENTICATE":
2370 // Post-connection-registration AUTHENTICATE is unsupported in
2371 // multi-upstream mode, or if the upstream doesn't support SASL
2372 uc := dc.upstream()
2373 if uc == nil || !uc.caps["sasl"] {
2374 return ircError{&irc.Message{
2375 Prefix: dc.srv.prefix(),
2376 Command: irc.ERR_SASLFAIL,
2377 Params: []string{dc.nick, "Upstream network authentication not supported"},
2378 }}
2379 }
2380
2381 credentials, err := dc.handleAuthenticateCommand(msg)
2382 if err != nil {
2383 return err
2384 }
2385
2386 if credentials != nil {
2387 if uc.saslClient != nil {
2388 dc.endSASL(&irc.Message{
2389 Prefix: dc.srv.prefix(),
2390 Command: irc.ERR_SASLFAIL,
2391 Params: []string{dc.nick, "Another authentication attempt is already in progress"},
2392 })
2393 return nil
2394 }
2395
2396 uc.logger.Printf("starting post-registration SASL PLAIN authentication with username %q", credentials.plainUsername)
2397 uc.saslClient = sasl.NewPlainClient("", credentials.plainUsername, credentials.plainPassword)
2398 uc.enqueueCommand(dc, &irc.Message{
2399 Command: "AUTHENTICATE",
2400 Params: []string{"PLAIN"},
2401 })
2402 }
2403 case "MONITOR":
2404 // MONITOR is unsupported in multi-upstream mode
2405 uc := dc.upstream()
2406 if uc == nil {
2407 return newUnknownCommandError(msg.Command)
2408 }
2409
2410 var subcommand string
2411 if err := parseMessageParams(msg, &subcommand); err != nil {
2412 return err
2413 }
2414
2415 switch strings.ToUpper(subcommand) {
2416 case "+", "-":
2417 var targets string
2418 if err := parseMessageParams(msg, nil, &targets); err != nil {
2419 return err
2420 }
2421 for _, target := range strings.Split(targets, ",") {
2422 if subcommand == "+" {
2423 // Hard limit, just to avoid having downstreams fill our map
2424 if len(dc.monitored.innerMap) >= 1000 {
2425 dc.SendMessage(&irc.Message{
2426 Prefix: dc.srv.prefix(),
2427 Command: irc.ERR_MONLISTFULL,
2428 Params: []string{dc.nick, "1000", target, "Bouncer monitor list is full"},
2429 })
2430 continue
2431 }
2432
2433 dc.monitored.SetValue(target, nil)
2434
2435 if uc.monitored.Has(target) {
2436 cmd := irc.RPL_MONOFFLINE
2437 if online := uc.monitored.Value(target); online {
2438 cmd = irc.RPL_MONONLINE
2439 }
2440
2441 dc.SendMessage(&irc.Message{
2442 Prefix: dc.srv.prefix(),
2443 Command: cmd,
2444 Params: []string{dc.nick, target},
2445 })
2446 }
2447 } else {
2448 dc.monitored.Delete(target)
2449 }
2450 }
2451 uc.updateMonitor()
2452 case "C": // clear
2453 dc.monitored = newCasemapMap(0)
2454 uc.updateMonitor()
2455 case "L": // list
2456 // TODO: be less lazy and pack the list
2457 for _, entry := range dc.monitored.innerMap {
2458 dc.SendMessage(&irc.Message{
2459 Prefix: dc.srv.prefix(),
2460 Command: irc.RPL_MONLIST,
2461 Params: []string{dc.nick, entry.originalKey},
2462 })
2463 }
2464 dc.SendMessage(&irc.Message{
2465 Prefix: dc.srv.prefix(),
2466 Command: irc.RPL_ENDOFMONLIST,
2467 Params: []string{dc.nick, "End of MONITOR list"},
2468 })
2469 case "S": // status
2470 // TODO: be less lazy and pack the lists
2471 for _, entry := range dc.monitored.innerMap {
2472 target := entry.originalKey
2473
2474 cmd := irc.RPL_MONOFFLINE
2475 if online := uc.monitored.Value(target); online {
2476 cmd = irc.RPL_MONONLINE
2477 }
2478
2479 dc.SendMessage(&irc.Message{
2480 Prefix: dc.srv.prefix(),
2481 Command: cmd,
2482 Params: []string{dc.nick, target},
2483 })
2484 }
2485 }
2486 case "CHATHISTORY":
2487 var subcommand string
2488 if err := parseMessageParams(msg, &subcommand); err != nil {
2489 return err
2490 }
2491 var target, limitStr string
2492 var boundsStr [2]string
2493 switch subcommand {
2494 case "AFTER", "BEFORE", "LATEST":
2495 if err := parseMessageParams(msg, nil, &target, &boundsStr[0], &limitStr); err != nil {
2496 return err
2497 }
2498 case "BETWEEN":
2499 if err := parseMessageParams(msg, nil, &target, &boundsStr[0], &boundsStr[1], &limitStr); err != nil {
2500 return err
2501 }
2502 case "TARGETS":
2503 if dc.network == nil {
2504 // Either an unbound bouncer network, in which case we should return no targets,
2505 // or a multi-upstream downstream, but we don't support CHATHISTORY TARGETS for those yet.
2506 dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef irc.TagValue) {})
2507 return nil
2508 }
2509 if err := parseMessageParams(msg, nil, &boundsStr[0], &boundsStr[1], &limitStr); err != nil {
2510 return err
2511 }
2512 default:
2513 // TODO: support AROUND
2514 return ircError{&irc.Message{
2515 Command: "FAIL",
2516 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, "Unknown command"},
2517 }}
2518 }
2519
2520 // We don't save history for our service
2521 if casemapASCII(target) == serviceNickCM {
2522 dc.SendBatch("chathistory", []string{target}, nil, func(batchRef irc.TagValue) {})
2523 return nil
2524 }
2525
2526 store, ok := dc.user.msgStore.(chatHistoryMessageStore)
2527 if !ok {
2528 return ircError{&irc.Message{
2529 Command: irc.ERR_UNKNOWNCOMMAND,
2530 Params: []string{dc.nick, "CHATHISTORY", "Unknown command"},
2531 }}
2532 }
2533
2534 network, entity, err := dc.unmarshalEntityNetwork(target)
2535 if err != nil {
2536 return err
2537 }
2538 entity = network.casemap(entity)
2539
2540 // TODO: support msgid criteria
2541 var bounds [2]time.Time
2542 bounds[0] = parseChatHistoryBound(boundsStr[0])
2543 if subcommand == "LATEST" && boundsStr[0] == "*" {
2544 bounds[0] = time.Now()
2545 } else if bounds[0].IsZero() {
2546 return ircError{&irc.Message{
2547 Command: "FAIL",
2548 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[0], "Invalid first bound"},
2549 }}
2550 }
2551
2552 if boundsStr[1] != "" {
2553 bounds[1] = parseChatHistoryBound(boundsStr[1])
2554 if bounds[1].IsZero() {
2555 return ircError{&irc.Message{
2556 Command: "FAIL",
2557 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[1], "Invalid second bound"},
2558 }}
2559 }
2560 }
2561
2562 limit, err := strconv.Atoi(limitStr)
2563 if err != nil || limit < 0 || limit > chatHistoryLimit {
2564 return ircError{&irc.Message{
2565 Command: "FAIL",
2566 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, limitStr, "Invalid limit"},
2567 }}
2568 }
2569
2570 eventPlayback := dc.caps["draft/event-playback"]
2571
2572 var history []*irc.Message
2573 switch subcommand {
2574 case "BEFORE", "LATEST":
2575 history, err = store.LoadBeforeTime(ctx, &network.Network, entity, bounds[0], time.Time{}, limit, eventPlayback)
2576 case "AFTER":
2577 history, err = store.LoadAfterTime(ctx, &network.Network, entity, bounds[0], time.Now(), limit, eventPlayback)
2578 case "BETWEEN":
2579 if bounds[0].Before(bounds[1]) {
2580 history, err = store.LoadAfterTime(ctx, &network.Network, entity, bounds[0], bounds[1], limit, eventPlayback)
2581 } else {
2582 history, err = store.LoadBeforeTime(ctx, &network.Network, entity, bounds[0], bounds[1], limit, eventPlayback)
2583 }
2584 case "TARGETS":
2585 // TODO: support TARGETS in multi-upstream mode
2586 targets, err := store.ListTargets(ctx, &network.Network, bounds[0], bounds[1], limit, eventPlayback)
2587 if err != nil {
2588 dc.logger.Printf("failed fetching targets for chathistory: %v", err)
2589 return ircError{&irc.Message{
2590 Command: "FAIL",
2591 Params: []string{"CHATHISTORY", "MESSAGE_ERROR", subcommand, "Failed to retrieve targets"},
2592 }}
2593 }
2594
2595 dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef irc.TagValue) {
2596 for _, target := range targets {
2597 if ch := network.channels.Value(target.Name); ch != nil && ch.Detached {
2598 continue
2599 }
2600
2601 dc.SendMessage(&irc.Message{
2602 Tags: irc.Tags{"batch": batchRef},
2603 Prefix: dc.srv.prefix(),
2604 Command: "CHATHISTORY",
2605 Params: []string{"TARGETS", target.Name, target.LatestMessage.UTC().Format(serverTimeLayout)},
2606 })
2607 }
2608 })
2609
2610 return nil
2611 }
2612 if err != nil {
2613 dc.logger.Printf("failed fetching %q messages for chathistory: %v", target, err)
2614 return newChatHistoryError(subcommand, target)
2615 }
2616
2617 dc.SendBatch("chathistory", []string{target}, nil, func(batchRef irc.TagValue) {
2618 for _, msg := range history {
2619 msg.Tags["batch"] = batchRef
2620 dc.SendMessage(dc.marshalMessage(msg, network))
2621 }
2622 })
2623 case "BOUNCER":
2624 var subcommand string
2625 if err := parseMessageParams(msg, &subcommand); err != nil {
2626 return err
2627 }
2628
2629 switch strings.ToUpper(subcommand) {
2630 case "BIND":
2631 return ircError{&irc.Message{
2632 Command: "FAIL",
2633 Params: []string{"BOUNCER", "REGISTRATION_IS_COMPLETED", "BIND", "Cannot bind to a network after registration"},
2634 }}
2635 case "LISTNETWORKS":
2636 dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
2637 dc.user.forEachNetwork(func(network *network) {
2638 idStr := fmt.Sprintf("%v", network.ID)
2639 attrs := getNetworkAttrs(network)
2640 dc.SendMessage(&irc.Message{
2641 Tags: irc.Tags{"batch": batchRef},
2642 Prefix: dc.srv.prefix(),
2643 Command: "BOUNCER",
2644 Params: []string{"NETWORK", idStr, attrs.String()},
2645 })
2646 })
2647 })
2648 case "ADDNETWORK":
2649 var attrsStr string
2650 if err := parseMessageParams(msg, nil, &attrsStr); err != nil {
2651 return err
2652 }
2653 attrs := irc.ParseTags(attrsStr)
2654
2655 record := &Network{Nick: dc.nick, Enabled: true}
2656 if err := updateNetworkAttrs(record, attrs, subcommand); err != nil {
2657 return err
2658 }
2659
2660 if record.Nick == dc.user.Username {
2661 record.Nick = ""
2662 }
2663 if record.Realname == dc.user.Realname {
2664 record.Realname = ""
2665 }
2666
2667 network, err := dc.user.createNetwork(ctx, record)
2668 if err != nil {
2669 return ircError{&irc.Message{
2670 Command: "FAIL",
2671 Params: []string{"BOUNCER", "UNKNOWN_ERROR", subcommand, fmt.Sprintf("Failed to create network: %v", err)},
2672 }}
2673 }
2674
2675 dc.SendMessage(&irc.Message{
2676 Prefix: dc.srv.prefix(),
2677 Command: "BOUNCER",
2678 Params: []string{"ADDNETWORK", fmt.Sprintf("%v", network.ID)},
2679 })
2680 case "CHANGENETWORK":
2681 var idStr, attrsStr string
2682 if err := parseMessageParams(msg, nil, &idStr, &attrsStr); err != nil {
2683 return err
2684 }
2685 id, err := parseBouncerNetID(subcommand, idStr)
2686 if err != nil {
2687 return err
2688 }
2689 attrs := irc.ParseTags(attrsStr)
2690
2691 net := dc.user.getNetworkByID(id)
2692 if net == nil {
2693 return ircError{&irc.Message{
2694 Command: "FAIL",
2695 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, idStr, "Invalid network ID"},
2696 }}
2697 }
2698
2699 record := net.Network // copy network record because we'll mutate it
2700 if err := updateNetworkAttrs(&record, attrs, subcommand); err != nil {
2701 return err
2702 }
2703
2704 if record.Nick == dc.user.Username {
2705 record.Nick = ""
2706 }
2707 if record.Realname == dc.user.Realname {
2708 record.Realname = ""
2709 }
2710
2711 _, err = dc.user.updateNetwork(ctx, &record)
2712 if err != nil {
2713 return ircError{&irc.Message{
2714 Command: "FAIL",
2715 Params: []string{"BOUNCER", "UNKNOWN_ERROR", subcommand, fmt.Sprintf("Failed to update network: %v", err)},
2716 }}
2717 }
2718
2719 dc.SendMessage(&irc.Message{
2720 Prefix: dc.srv.prefix(),
2721 Command: "BOUNCER",
2722 Params: []string{"CHANGENETWORK", idStr},
2723 })
2724 case "DELNETWORK":
2725 var idStr string
2726 if err := parseMessageParams(msg, nil, &idStr); err != nil {
2727 return err
2728 }
2729 id, err := parseBouncerNetID(subcommand, idStr)
2730 if err != nil {
2731 return err
2732 }
2733
2734 net := dc.user.getNetworkByID(id)
2735 if net == nil {
2736 return ircError{&irc.Message{
2737 Command: "FAIL",
2738 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, idStr, "Invalid network ID"},
2739 }}
2740 }
2741
2742 if err := dc.user.deleteNetwork(ctx, net.ID); err != nil {
2743 return err
2744 }
2745
2746 dc.SendMessage(&irc.Message{
2747 Prefix: dc.srv.prefix(),
2748 Command: "BOUNCER",
2749 Params: []string{"DELNETWORK", idStr},
2750 })
2751 default:
2752 return ircError{&irc.Message{
2753 Command: "FAIL",
2754 Params: []string{"BOUNCER", "UNKNOWN_COMMAND", subcommand, "Unknown subcommand"},
2755 }}
2756 }
2757 default:
2758 dc.logger.Printf("unhandled message: %v", msg)
2759
2760 // Only forward unknown commands in single-upstream mode
2761 uc := dc.upstream()
2762 if uc == nil {
2763 return newUnknownCommandError(msg.Command)
2764 }
2765
2766 uc.SendMessageLabeled(dc.id, msg)
2767 }
2768 return nil
2769}
2770
2771func (dc *downstreamConn) handleNickServPRIVMSG(ctx context.Context, uc *upstreamConn, text string) {
2772 username, password, ok := parseNickServCredentials(text, uc.nick)
2773 if ok {
2774 uc.network.autoSaveSASLPlain(ctx, username, password)
2775 }
2776}
2777
2778func parseNickServCredentials(text, nick string) (username, password string, ok bool) {
2779 fields := strings.Fields(text)
2780 if len(fields) < 2 {
2781 return "", "", false
2782 }
2783 cmd := strings.ToUpper(fields[0])
2784 params := fields[1:]
2785 switch cmd {
2786 case "REGISTER":
2787 username = nick
2788 password = params[0]
2789 case "IDENTIFY":
2790 if len(params) == 1 {
2791 username = nick
2792 password = params[0]
2793 } else {
2794 username = params[0]
2795 password = params[1]
2796 }
2797 case "SET":
2798 if len(params) == 2 && strings.EqualFold(params[0], "PASSWORD") {
2799 username = nick
2800 password = params[1]
2801 }
2802 default:
2803 return "", "", false
2804 }
2805 return username, password, true
2806}
Note: See TracBrowser for help on using the repository browser.