source: code/trunk/downstream.go@ 793

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

downstream: re-format illegalNickChars doc comment

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