source: code/trunk/downstream.go@ 726

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

Return more descriptive auth failure errors

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