source: code/trunk/downstream.go@ 789

Last change on this file since 789 was 789, checked in by contact, 3 years ago

downstream: be less strict when picking up client name

Allow e.g. the SASL username to contain "username/network" and the
raw username to contain "username@client", for instance.

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