source: code/trunk/downstream.go@ 786

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

Introduce formatServerTime

It's too easy to forget to convert to UTC.

File size: 77.6 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 return fmt.Errorf("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 if dc.clientName == "" && dc.networkName == "" {
1291 _, dc.clientName, dc.networkName = unmarshalUsername(dc.rawUsername)
1292 }
1293
1294 dc.registered = true
1295 dc.logger.Printf("registration complete for user %q", dc.user.Username)
1296 return nil
1297}
1298
1299func (dc *downstreamConn) loadNetwork(ctx context.Context) error {
1300 if dc.networkName == "" {
1301 return nil
1302 }
1303
1304 network := dc.user.getNetwork(dc.networkName)
1305 if network == nil {
1306 addr := dc.networkName
1307 if !strings.ContainsRune(addr, ':') {
1308 addr = addr + ":6697"
1309 }
1310
1311 dc.logger.Printf("trying to connect to new network %q", addr)
1312 if err := sanityCheckServer(ctx, addr); err != nil {
1313 dc.logger.Printf("failed to connect to %q: %v", addr, err)
1314 return ircError{&irc.Message{
1315 Command: irc.ERR_PASSWDMISMATCH,
1316 Params: []string{dc.nick, fmt.Sprintf("Failed to connect to %q", dc.networkName)},
1317 }}
1318 }
1319
1320 // Some clients only allow specifying the nickname (and use the
1321 // nickname as a username too). Strip the network name from the
1322 // nickname when auto-saving networks.
1323 nick, _, _ := unmarshalUsername(dc.nick)
1324
1325 dc.logger.Printf("auto-saving network %q", dc.networkName)
1326 var err error
1327 network, err = dc.user.createNetwork(ctx, &Network{
1328 Addr: dc.networkName,
1329 Nick: nick,
1330 Enabled: true,
1331 })
1332 if err != nil {
1333 return err
1334 }
1335 }
1336
1337 dc.network = network
1338 return nil
1339}
1340
1341func (dc *downstreamConn) welcome(ctx context.Context) error {
1342 if dc.user == nil || !dc.registered {
1343 panic("tried to welcome an unregistered connection")
1344 }
1345
1346 remoteAddr := dc.conn.RemoteAddr().String()
1347 dc.logger = &prefixLogger{dc.srv.Logger, fmt.Sprintf("user %q: downstream %q: ", dc.user.Username, remoteAddr)}
1348
1349 // TODO: doing this might take some time. We should do it in dc.register
1350 // instead, but we'll potentially be adding a new network and this must be
1351 // done in the user goroutine.
1352 if err := dc.loadNetwork(ctx); err != nil {
1353 return err
1354 }
1355
1356 if dc.network == nil && !dc.caps["soju.im/bouncer-networks"] && dc.srv.Config().MultiUpstream {
1357 dc.isMultiUpstream = true
1358 }
1359
1360 dc.updateSupportedCaps()
1361
1362 isupport := []string{
1363 fmt.Sprintf("CHATHISTORY=%v", chatHistoryLimit),
1364 "CASEMAPPING=ascii",
1365 }
1366
1367 if dc.network != nil {
1368 isupport = append(isupport, fmt.Sprintf("BOUNCER_NETID=%v", dc.network.ID))
1369 }
1370 if title := dc.srv.Config().Title; dc.network == nil && title != "" {
1371 isupport = append(isupport, "NETWORK="+encodeISUPPORT(title))
1372 }
1373 if dc.network == nil && !dc.isMultiUpstream {
1374 isupport = append(isupport, "WHOX")
1375 }
1376
1377 if uc := dc.upstream(); uc != nil {
1378 for k := range passthroughIsupport {
1379 v, ok := uc.isupport[k]
1380 if !ok {
1381 continue
1382 }
1383 if v != nil {
1384 isupport = append(isupport, fmt.Sprintf("%v=%v", k, *v))
1385 } else {
1386 isupport = append(isupport, k)
1387 }
1388 }
1389 }
1390
1391 dc.SendMessage(&irc.Message{
1392 Prefix: dc.srv.prefix(),
1393 Command: irc.RPL_WELCOME,
1394 Params: []string{dc.nick, "Welcome to soju, " + dc.nick},
1395 })
1396 dc.SendMessage(&irc.Message{
1397 Prefix: dc.srv.prefix(),
1398 Command: irc.RPL_YOURHOST,
1399 Params: []string{dc.nick, "Your host is " + dc.srv.Config().Hostname},
1400 })
1401 dc.SendMessage(&irc.Message{
1402 Prefix: dc.srv.prefix(),
1403 Command: irc.RPL_MYINFO,
1404 Params: []string{dc.nick, dc.srv.Config().Hostname, "soju", "aiwroO", "OovaimnqpsrtklbeI"},
1405 })
1406 for _, msg := range generateIsupport(dc.srv.prefix(), dc.nick, isupport) {
1407 dc.SendMessage(msg)
1408 }
1409 if uc := dc.upstream(); uc != nil {
1410 dc.SendMessage(&irc.Message{
1411 Prefix: dc.srv.prefix(),
1412 Command: irc.RPL_UMODEIS,
1413 Params: []string{dc.nick, "+" + string(uc.modes)},
1414 })
1415 }
1416 if dc.network == nil && !dc.isMultiUpstream && dc.user.Admin {
1417 dc.SendMessage(&irc.Message{
1418 Prefix: dc.srv.prefix(),
1419 Command: irc.RPL_UMODEIS,
1420 Params: []string{dc.nick, "+o"},
1421 })
1422 }
1423
1424 dc.updateNick()
1425 dc.updateRealname()
1426 dc.updateAccount()
1427
1428 if motd := dc.user.srv.Config().MOTD; motd != "" && dc.network == nil {
1429 for _, msg := range generateMOTD(dc.srv.prefix(), dc.nick, motd) {
1430 dc.SendMessage(msg)
1431 }
1432 } else {
1433 motdHint := "No MOTD"
1434 if dc.network != nil {
1435 motdHint = "Use /motd to read the message of the day"
1436 }
1437 dc.SendMessage(&irc.Message{
1438 Prefix: dc.srv.prefix(),
1439 Command: irc.ERR_NOMOTD,
1440 Params: []string{dc.nick, motdHint},
1441 })
1442 }
1443
1444 if dc.caps["soju.im/bouncer-networks-notify"] {
1445 dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
1446 for _, network := range dc.user.networks {
1447 idStr := fmt.Sprintf("%v", network.ID)
1448 attrs := getNetworkAttrs(network)
1449 dc.SendMessage(&irc.Message{
1450 Tags: irc.Tags{"batch": batchRef},
1451 Prefix: dc.srv.prefix(),
1452 Command: "BOUNCER",
1453 Params: []string{"NETWORK", idStr, attrs.String()},
1454 })
1455 }
1456 })
1457 }
1458
1459 dc.forEachUpstream(func(uc *upstreamConn) {
1460 for _, entry := range uc.channels.innerMap {
1461 ch := entry.value.(*upstreamChannel)
1462 if !ch.complete {
1463 continue
1464 }
1465 record := uc.network.channels.Value(ch.Name)
1466 if record != nil && record.Detached {
1467 continue
1468 }
1469
1470 dc.SendMessage(&irc.Message{
1471 Prefix: dc.prefix(),
1472 Command: "JOIN",
1473 Params: []string{dc.marshalEntity(ch.conn.network, ch.Name)},
1474 })
1475
1476 forwardChannel(ctx, dc, ch)
1477 }
1478 })
1479
1480 dc.forEachNetwork(func(net *network) {
1481 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
1482 return
1483 }
1484
1485 // Only send history if we're the first connected client with that name
1486 // for the network
1487 firstClient := true
1488 dc.user.forEachDownstream(func(c *downstreamConn) {
1489 if c != dc && c.clientName == dc.clientName && c.network == dc.network {
1490 firstClient = false
1491 }
1492 })
1493 if firstClient {
1494 net.delivered.ForEachTarget(func(target string) {
1495 lastDelivered := net.delivered.LoadID(target, dc.clientName)
1496 if lastDelivered == "" {
1497 return
1498 }
1499
1500 dc.sendTargetBacklog(ctx, net, target, lastDelivered)
1501
1502 // Fast-forward history to last message
1503 targetCM := net.casemap(target)
1504 lastID, err := dc.user.msgStore.LastMsgID(&net.Network, targetCM, time.Now())
1505 if err != nil {
1506 dc.logger.Printf("failed to get last message ID: %v", err)
1507 return
1508 }
1509 net.delivered.StoreID(target, dc.clientName, lastID)
1510 })
1511 }
1512 })
1513
1514 return nil
1515}
1516
1517// messageSupportsBacklog checks whether the provided message can be sent as
1518// part of an history batch.
1519func (dc *downstreamConn) messageSupportsBacklog(msg *irc.Message) bool {
1520 // Don't replay all messages, because that would mess up client
1521 // state. For instance we just sent the list of users, sending
1522 // PART messages for one of these users would be incorrect.
1523 switch msg.Command {
1524 case "PRIVMSG", "NOTICE":
1525 return true
1526 }
1527 return false
1528}
1529
1530func (dc *downstreamConn) sendTargetBacklog(ctx context.Context, net *network, target, msgID string) {
1531 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
1532 return
1533 }
1534
1535 ch := net.channels.Value(target)
1536
1537 ctx, cancel := context.WithTimeout(ctx, backlogTimeout)
1538 defer cancel()
1539
1540 targetCM := net.casemap(target)
1541 history, err := dc.user.msgStore.LoadLatestID(ctx, &net.Network, targetCM, msgID, backlogLimit)
1542 if err != nil {
1543 dc.logger.Printf("failed to send backlog for %q: %v", target, err)
1544 return
1545 }
1546
1547 dc.SendBatch("chathistory", []string{dc.marshalEntity(net, target)}, nil, func(batchRef irc.TagValue) {
1548 for _, msg := range history {
1549 if ch != nil && ch.Detached {
1550 if net.detachedMessageNeedsRelay(ch, msg) {
1551 dc.relayDetachedMessage(net, msg)
1552 }
1553 } else {
1554 msg.Tags["batch"] = batchRef
1555 dc.SendMessage(dc.marshalMessage(msg, net))
1556 }
1557 }
1558 })
1559}
1560
1561func (dc *downstreamConn) relayDetachedMessage(net *network, msg *irc.Message) {
1562 if msg.Command != "PRIVMSG" && msg.Command != "NOTICE" {
1563 return
1564 }
1565
1566 sender := msg.Prefix.Name
1567 target, text := msg.Params[0], msg.Params[1]
1568 if net.isHighlight(msg) {
1569 sendServiceNOTICE(dc, fmt.Sprintf("highlight in %v: <%v> %v", dc.marshalEntity(net, target), sender, text))
1570 } else {
1571 sendServiceNOTICE(dc, fmt.Sprintf("message in %v: <%v> %v", dc.marshalEntity(net, target), sender, text))
1572 }
1573}
1574
1575func (dc *downstreamConn) runUntilRegistered() error {
1576 ctx, cancel := context.WithTimeout(context.TODO(), downstreamRegisterTimeout)
1577 defer cancel()
1578
1579 // Close the connection with an error if the deadline is exceeded
1580 go func() {
1581 <-ctx.Done()
1582 if err := ctx.Err(); err == context.DeadlineExceeded {
1583 dc.SendMessage(&irc.Message{
1584 Prefix: dc.srv.prefix(),
1585 Command: "ERROR",
1586 Params: []string{"Connection registration timed out"},
1587 })
1588 dc.Close()
1589 }
1590 }()
1591
1592 for !dc.registered {
1593 msg, err := dc.ReadMessage()
1594 if err != nil {
1595 return fmt.Errorf("failed to read IRC command: %w", err)
1596 }
1597
1598 err = dc.handleMessage(ctx, msg)
1599 if ircErr, ok := err.(ircError); ok {
1600 ircErr.Message.Prefix = dc.srv.prefix()
1601 dc.SendMessage(ircErr.Message)
1602 } else if err != nil {
1603 return fmt.Errorf("failed to handle IRC command %q: %v", msg, err)
1604 }
1605 }
1606
1607 return nil
1608}
1609
1610func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.Message) error {
1611 switch msg.Command {
1612 case "CAP":
1613 var subCmd string
1614 if err := parseMessageParams(msg, &subCmd); err != nil {
1615 return err
1616 }
1617 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
1618 return err
1619 }
1620 case "PING":
1621 var source, destination string
1622 if err := parseMessageParams(msg, &source); err != nil {
1623 return err
1624 }
1625 if len(msg.Params) > 1 {
1626 destination = msg.Params[1]
1627 }
1628 hostname := dc.srv.Config().Hostname
1629 if destination != "" && destination != hostname {
1630 return ircError{&irc.Message{
1631 Command: irc.ERR_NOSUCHSERVER,
1632 Params: []string{dc.nick, destination, "No such server"},
1633 }}
1634 }
1635 dc.SendMessage(&irc.Message{
1636 Prefix: dc.srv.prefix(),
1637 Command: "PONG",
1638 Params: []string{hostname, source},
1639 })
1640 return nil
1641 case "PONG":
1642 if len(msg.Params) == 0 {
1643 return newNeedMoreParamsError(msg.Command)
1644 }
1645 token := msg.Params[len(msg.Params)-1]
1646 dc.handlePong(token)
1647 case "USER":
1648 return ircError{&irc.Message{
1649 Command: irc.ERR_ALREADYREGISTERED,
1650 Params: []string{dc.nick, "You may not reregister"},
1651 }}
1652 case "NICK":
1653 var rawNick string
1654 if err := parseMessageParams(msg, &rawNick); err != nil {
1655 return err
1656 }
1657
1658 nick := rawNick
1659 var upstream *upstreamConn
1660 if dc.upstream() == nil {
1661 uc, unmarshaledNick, err := dc.unmarshalEntity(nick)
1662 if err == nil { // NICK nick/network: NICK only on a specific upstream
1663 upstream = uc
1664 nick = unmarshaledNick
1665 }
1666 }
1667
1668 if nick == "" || strings.ContainsAny(nick, illegalNickChars) {
1669 return ircError{&irc.Message{
1670 Command: irc.ERR_ERRONEUSNICKNAME,
1671 Params: []string{dc.nick, rawNick, "contains illegal characters"},
1672 }}
1673 }
1674 if casemapASCII(nick) == serviceNickCM {
1675 return ircError{&irc.Message{
1676 Command: irc.ERR_NICKNAMEINUSE,
1677 Params: []string{dc.nick, rawNick, "Nickname reserved for bouncer service"},
1678 }}
1679 }
1680
1681 var err error
1682 dc.forEachNetwork(func(n *network) {
1683 if err != nil || (upstream != nil && upstream.network != n) {
1684 return
1685 }
1686 n.Nick = nick
1687 err = dc.srv.db.StoreNetwork(ctx, dc.user.ID, &n.Network)
1688 })
1689 if err != nil {
1690 return err
1691 }
1692
1693 dc.forEachUpstream(func(uc *upstreamConn) {
1694 if upstream != nil && upstream != uc {
1695 return
1696 }
1697 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
1698 Command: "NICK",
1699 Params: []string{nick},
1700 })
1701 })
1702
1703 if dc.upstream() == nil && upstream == nil && dc.nick != nick {
1704 dc.SendMessage(&irc.Message{
1705 Prefix: dc.prefix(),
1706 Command: "NICK",
1707 Params: []string{nick},
1708 })
1709 dc.nick = nick
1710 dc.nickCM = casemapASCII(dc.nick)
1711 }
1712 case "SETNAME":
1713 var realname string
1714 if err := parseMessageParams(msg, &realname); err != nil {
1715 return err
1716 }
1717
1718 // If the client just resets to the default, just wipe the per-network
1719 // preference
1720 storeRealname := realname
1721 if realname == dc.user.Realname {
1722 storeRealname = ""
1723 }
1724
1725 var storeErr error
1726 var needUpdate []Network
1727 dc.forEachNetwork(func(n *network) {
1728 // We only need to call updateNetwork for upstreams that don't
1729 // support setname
1730 if uc := n.conn; uc != nil && uc.caps["setname"] {
1731 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
1732 Command: "SETNAME",
1733 Params: []string{realname},
1734 })
1735
1736 n.Realname = storeRealname
1737 if err := dc.srv.db.StoreNetwork(ctx, dc.user.ID, &n.Network); err != nil {
1738 dc.logger.Printf("failed to store network realname: %v", err)
1739 storeErr = err
1740 }
1741 return
1742 }
1743
1744 record := n.Network // copy network record because we'll mutate it
1745 record.Realname = storeRealname
1746 needUpdate = append(needUpdate, record)
1747 })
1748
1749 // Walk the network list as a second step, because updateNetwork
1750 // mutates the original list
1751 for _, record := range needUpdate {
1752 if _, err := dc.user.updateNetwork(ctx, &record); err != nil {
1753 dc.logger.Printf("failed to update network realname: %v", err)
1754 storeErr = err
1755 }
1756 }
1757 if storeErr != nil {
1758 return ircError{&irc.Message{
1759 Command: "FAIL",
1760 Params: []string{"SETNAME", "CANNOT_CHANGE_REALNAME", "Failed to update realname"},
1761 }}
1762 }
1763
1764 if dc.upstream() == nil {
1765 dc.SendMessage(&irc.Message{
1766 Prefix: dc.prefix(),
1767 Command: "SETNAME",
1768 Params: []string{realname},
1769 })
1770 }
1771 case "JOIN":
1772 var namesStr string
1773 if err := parseMessageParams(msg, &namesStr); err != nil {
1774 return err
1775 }
1776
1777 var keys []string
1778 if len(msg.Params) > 1 {
1779 keys = strings.Split(msg.Params[1], ",")
1780 }
1781
1782 for i, name := range strings.Split(namesStr, ",") {
1783 uc, upstreamName, err := dc.unmarshalEntity(name)
1784 if err != nil {
1785 return err
1786 }
1787
1788 var key string
1789 if len(keys) > i {
1790 key = keys[i]
1791 }
1792
1793 if !uc.isChannel(upstreamName) {
1794 dc.SendMessage(&irc.Message{
1795 Prefix: dc.srv.prefix(),
1796 Command: irc.ERR_NOSUCHCHANNEL,
1797 Params: []string{name, "Not a channel name"},
1798 })
1799 continue
1800 }
1801
1802 // Most servers ignore duplicate JOIN messages. We ignore them here
1803 // because some clients automatically send JOIN messages in bulk
1804 // when reconnecting to the bouncer. We don't want to flood the
1805 // upstream connection with these.
1806 if !uc.channels.Has(upstreamName) {
1807 params := []string{upstreamName}
1808 if key != "" {
1809 params = append(params, key)
1810 }
1811 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
1812 Command: "JOIN",
1813 Params: params,
1814 })
1815 }
1816
1817 ch := uc.network.channels.Value(upstreamName)
1818 if ch != nil {
1819 // Don't clear the channel key if there's one set
1820 // TODO: add a way to unset the channel key
1821 if key != "" {
1822 ch.Key = key
1823 }
1824 uc.network.attach(ctx, ch)
1825 } else {
1826 ch = &Channel{
1827 Name: upstreamName,
1828 Key: key,
1829 }
1830 uc.network.channels.SetValue(upstreamName, ch)
1831 }
1832 if err := dc.srv.db.StoreChannel(ctx, uc.network.ID, ch); err != nil {
1833 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1834 }
1835 }
1836 case "PART":
1837 var namesStr string
1838 if err := parseMessageParams(msg, &namesStr); err != nil {
1839 return err
1840 }
1841
1842 var reason string
1843 if len(msg.Params) > 1 {
1844 reason = msg.Params[1]
1845 }
1846
1847 for _, name := range strings.Split(namesStr, ",") {
1848 uc, upstreamName, err := dc.unmarshalEntity(name)
1849 if err != nil {
1850 return err
1851 }
1852
1853 if strings.EqualFold(reason, "detach") {
1854 ch := uc.network.channels.Value(upstreamName)
1855 if ch != nil {
1856 uc.network.detach(ch)
1857 } else {
1858 ch = &Channel{
1859 Name: name,
1860 Detached: true,
1861 }
1862 uc.network.channels.SetValue(upstreamName, ch)
1863 }
1864 if err := dc.srv.db.StoreChannel(ctx, uc.network.ID, ch); err != nil {
1865 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1866 }
1867 } else {
1868 params := []string{upstreamName}
1869 if reason != "" {
1870 params = append(params, reason)
1871 }
1872 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
1873 Command: "PART",
1874 Params: params,
1875 })
1876
1877 if err := uc.network.deleteChannel(ctx, upstreamName); err != nil {
1878 dc.logger.Printf("failed to delete channel %q: %v", upstreamName, err)
1879 }
1880 }
1881 }
1882 case "KICK":
1883 var channelStr, userStr string
1884 if err := parseMessageParams(msg, &channelStr, &userStr); err != nil {
1885 return err
1886 }
1887
1888 channels := strings.Split(channelStr, ",")
1889 users := strings.Split(userStr, ",")
1890
1891 var reason string
1892 if len(msg.Params) > 2 {
1893 reason = msg.Params[2]
1894 }
1895
1896 if len(channels) != 1 && len(channels) != len(users) {
1897 return ircError{&irc.Message{
1898 Command: irc.ERR_BADCHANMASK,
1899 Params: []string{dc.nick, channelStr, "Bad channel mask"},
1900 }}
1901 }
1902
1903 for i, user := range users {
1904 var channel string
1905 if len(channels) == 1 {
1906 channel = channels[0]
1907 } else {
1908 channel = channels[i]
1909 }
1910
1911 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1912 if err != nil {
1913 return err
1914 }
1915
1916 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1917 if err != nil {
1918 return err
1919 }
1920
1921 if ucChannel != ucUser {
1922 return ircError{&irc.Message{
1923 Command: irc.ERR_USERNOTINCHANNEL,
1924 Params: []string{dc.nick, user, channel, "They are on another network"},
1925 }}
1926 }
1927 uc := ucChannel
1928
1929 params := []string{upstreamChannel, upstreamUser}
1930 if reason != "" {
1931 params = append(params, reason)
1932 }
1933 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
1934 Command: "KICK",
1935 Params: params,
1936 })
1937 }
1938 case "MODE":
1939 var name string
1940 if err := parseMessageParams(msg, &name); err != nil {
1941 return err
1942 }
1943
1944 var modeStr string
1945 if len(msg.Params) > 1 {
1946 modeStr = msg.Params[1]
1947 }
1948
1949 if casemapASCII(name) == dc.nickCM {
1950 if modeStr != "" {
1951 if uc := dc.upstream(); uc != nil {
1952 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
1953 Command: "MODE",
1954 Params: []string{uc.nick, modeStr},
1955 })
1956 } else {
1957 dc.SendMessage(&irc.Message{
1958 Prefix: dc.srv.prefix(),
1959 Command: irc.ERR_UMODEUNKNOWNFLAG,
1960 Params: []string{dc.nick, "Cannot change user mode in multi-upstream mode"},
1961 })
1962 }
1963 } else {
1964 var userMode string
1965 if uc := dc.upstream(); uc != nil {
1966 userMode = string(uc.modes)
1967 }
1968
1969 dc.SendMessage(&irc.Message{
1970 Prefix: dc.srv.prefix(),
1971 Command: irc.RPL_UMODEIS,
1972 Params: []string{dc.nick, "+" + userMode},
1973 })
1974 }
1975 return nil
1976 }
1977
1978 uc, upstreamName, err := dc.unmarshalEntity(name)
1979 if err != nil {
1980 return err
1981 }
1982
1983 if !uc.isChannel(upstreamName) {
1984 return ircError{&irc.Message{
1985 Command: irc.ERR_USERSDONTMATCH,
1986 Params: []string{dc.nick, "Cannot change mode for other users"},
1987 }}
1988 }
1989
1990 if modeStr != "" {
1991 params := []string{upstreamName, modeStr}
1992 params = append(params, msg.Params[2:]...)
1993 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
1994 Command: "MODE",
1995 Params: params,
1996 })
1997 } else {
1998 ch := uc.channels.Value(upstreamName)
1999 if ch == nil {
2000 return ircError{&irc.Message{
2001 Command: irc.ERR_NOSUCHCHANNEL,
2002 Params: []string{dc.nick, name, "No such channel"},
2003 }}
2004 }
2005
2006 if ch.modes == nil {
2007 // we haven't received the initial RPL_CHANNELMODEIS yet
2008 // ignore the request, we will broadcast the modes later when we receive RPL_CHANNELMODEIS
2009 return nil
2010 }
2011
2012 modeStr, modeParams := ch.modes.Format()
2013 params := []string{dc.nick, name, modeStr}
2014 params = append(params, modeParams...)
2015
2016 dc.SendMessage(&irc.Message{
2017 Prefix: dc.srv.prefix(),
2018 Command: irc.RPL_CHANNELMODEIS,
2019 Params: params,
2020 })
2021 if ch.creationTime != "" {
2022 dc.SendMessage(&irc.Message{
2023 Prefix: dc.srv.prefix(),
2024 Command: rpl_creationtime,
2025 Params: []string{dc.nick, name, ch.creationTime},
2026 })
2027 }
2028 }
2029 case "TOPIC":
2030 var channel string
2031 if err := parseMessageParams(msg, &channel); err != nil {
2032 return err
2033 }
2034
2035 uc, upstreamName, err := dc.unmarshalEntity(channel)
2036 if err != nil {
2037 return err
2038 }
2039
2040 if len(msg.Params) > 1 { // setting topic
2041 topic := msg.Params[1]
2042 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
2043 Command: "TOPIC",
2044 Params: []string{upstreamName, topic},
2045 })
2046 } else { // getting topic
2047 ch := uc.channels.Value(upstreamName)
2048 if ch == nil {
2049 return ircError{&irc.Message{
2050 Command: irc.ERR_NOSUCHCHANNEL,
2051 Params: []string{dc.nick, upstreamName, "No such channel"},
2052 }}
2053 }
2054 sendTopic(dc, ch)
2055 }
2056 case "LIST":
2057 network := dc.network
2058 if network == nil && len(msg.Params) > 0 {
2059 var err error
2060 network, msg.Params[0], err = dc.unmarshalEntityNetwork(msg.Params[0])
2061 if err != nil {
2062 return err
2063 }
2064 }
2065 if network == nil {
2066 dc.SendMessage(&irc.Message{
2067 Prefix: dc.srv.prefix(),
2068 Command: irc.RPL_LISTEND,
2069 Params: []string{dc.nick, "LIST without a network suffix is not supported in multi-upstream mode"},
2070 })
2071 return nil
2072 }
2073
2074 uc := network.conn
2075 if uc == nil {
2076 dc.SendMessage(&irc.Message{
2077 Prefix: dc.srv.prefix(),
2078 Command: irc.RPL_LISTEND,
2079 Params: []string{dc.nick, "Disconnected from upstream server"},
2080 })
2081 return nil
2082 }
2083
2084 uc.enqueueCommand(dc, msg)
2085 case "NAMES":
2086 if len(msg.Params) == 0 {
2087 dc.SendMessage(&irc.Message{
2088 Prefix: dc.srv.prefix(),
2089 Command: irc.RPL_ENDOFNAMES,
2090 Params: []string{dc.nick, "*", "End of /NAMES list"},
2091 })
2092 return nil
2093 }
2094
2095 channels := strings.Split(msg.Params[0], ",")
2096 for _, channel := range channels {
2097 uc, upstreamName, err := dc.unmarshalEntity(channel)
2098 if err != nil {
2099 return err
2100 }
2101
2102 ch := uc.channels.Value(upstreamName)
2103 if ch != nil {
2104 sendNames(dc, ch)
2105 } else {
2106 // NAMES on a channel we have not joined, ask upstream
2107 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
2108 Command: "NAMES",
2109 Params: []string{upstreamName},
2110 })
2111 }
2112 }
2113 // For WHOX docs, see:
2114 // - http://faerion.sourceforge.net/doc/irc/whox.var
2115 // - https://github.com/quakenet/snircd/blob/master/doc/readme.who
2116 // Note, many features aren't widely implemented, such as flags and mask2
2117 case "WHO":
2118 if len(msg.Params) == 0 {
2119 // TODO: support WHO without parameters
2120 dc.SendMessage(&irc.Message{
2121 Prefix: dc.srv.prefix(),
2122 Command: irc.RPL_ENDOFWHO,
2123 Params: []string{dc.nick, "*", "End of /WHO list"},
2124 })
2125 return nil
2126 }
2127
2128 // Clients will use the first mask to match RPL_ENDOFWHO
2129 endOfWhoToken := msg.Params[0]
2130
2131 // TODO: add support for WHOX mask2
2132 mask := msg.Params[0]
2133 var options string
2134 if len(msg.Params) > 1 {
2135 options = msg.Params[1]
2136 }
2137
2138 optionsParts := strings.SplitN(options, "%", 2)
2139 // TODO: add support for WHOX flags in optionsParts[0]
2140 var fields, whoxToken string
2141 if len(optionsParts) == 2 {
2142 optionsParts := strings.SplitN(optionsParts[1], ",", 2)
2143 fields = strings.ToLower(optionsParts[0])
2144 if len(optionsParts) == 2 && strings.Contains(fields, "t") {
2145 whoxToken = optionsParts[1]
2146 }
2147 }
2148
2149 // TODO: support mixed bouncer/upstream WHO queries
2150 maskCM := casemapASCII(mask)
2151 if dc.network == nil && maskCM == dc.nickCM {
2152 // TODO: support AWAY (H/G) in self WHO reply
2153 flags := "H"
2154 if dc.user.Admin {
2155 flags += "*"
2156 }
2157 info := whoxInfo{
2158 Token: whoxToken,
2159 Username: dc.user.Username,
2160 Hostname: dc.hostname,
2161 Server: dc.srv.Config().Hostname,
2162 Nickname: dc.nick,
2163 Flags: flags,
2164 Account: dc.user.Username,
2165 Realname: dc.realname,
2166 }
2167 dc.SendMessage(generateWHOXReply(dc.srv.prefix(), dc.nick, fields, &info))
2168 dc.SendMessage(&irc.Message{
2169 Prefix: dc.srv.prefix(),
2170 Command: irc.RPL_ENDOFWHO,
2171 Params: []string{dc.nick, endOfWhoToken, "End of /WHO list"},
2172 })
2173 return nil
2174 }
2175 if maskCM == serviceNickCM {
2176 info := whoxInfo{
2177 Token: whoxToken,
2178 Username: servicePrefix.User,
2179 Hostname: servicePrefix.Host,
2180 Server: dc.srv.Config().Hostname,
2181 Nickname: serviceNick,
2182 Flags: "H*",
2183 Account: serviceNick,
2184 Realname: serviceRealname,
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
2195 // TODO: properly support WHO masks
2196 uc, upstreamMask, err := dc.unmarshalEntity(mask)
2197 if err != nil {
2198 return err
2199 }
2200
2201 params := []string{upstreamMask}
2202 if options != "" {
2203 params = append(params, options)
2204 }
2205
2206 uc.enqueueCommand(dc, &irc.Message{
2207 Command: "WHO",
2208 Params: params,
2209 })
2210 case "WHOIS":
2211 if len(msg.Params) == 0 {
2212 return ircError{&irc.Message{
2213 Command: irc.ERR_NONICKNAMEGIVEN,
2214 Params: []string{dc.nick, "No nickname given"},
2215 }}
2216 }
2217
2218 var target, mask string
2219 if len(msg.Params) == 1 {
2220 target = ""
2221 mask = msg.Params[0]
2222 } else {
2223 target = msg.Params[0]
2224 mask = msg.Params[1]
2225 }
2226 // TODO: support multiple WHOIS users
2227 if i := strings.IndexByte(mask, ','); i >= 0 {
2228 mask = mask[:i]
2229 }
2230
2231 if dc.network == nil && casemapASCII(mask) == dc.nickCM {
2232 dc.SendMessage(&irc.Message{
2233 Prefix: dc.srv.prefix(),
2234 Command: irc.RPL_WHOISUSER,
2235 Params: []string{dc.nick, dc.nick, dc.user.Username, dc.hostname, "*", dc.realname},
2236 })
2237 dc.SendMessage(&irc.Message{
2238 Prefix: dc.srv.prefix(),
2239 Command: irc.RPL_WHOISSERVER,
2240 Params: []string{dc.nick, dc.nick, dc.srv.Config().Hostname, "soju"},
2241 })
2242 if dc.user.Admin {
2243 dc.SendMessage(&irc.Message{
2244 Prefix: dc.srv.prefix(),
2245 Command: irc.RPL_WHOISOPERATOR,
2246 Params: []string{dc.nick, dc.nick, "is a bouncer administrator"},
2247 })
2248 }
2249 dc.SendMessage(&irc.Message{
2250 Prefix: dc.srv.prefix(),
2251 Command: rpl_whoisaccount,
2252 Params: []string{dc.nick, dc.nick, dc.user.Username, "is logged in as"},
2253 })
2254 dc.SendMessage(&irc.Message{
2255 Prefix: dc.srv.prefix(),
2256 Command: irc.RPL_ENDOFWHOIS,
2257 Params: []string{dc.nick, dc.nick, "End of /WHOIS list"},
2258 })
2259 return nil
2260 }
2261 if casemapASCII(mask) == serviceNickCM {
2262 dc.SendMessage(&irc.Message{
2263 Prefix: dc.srv.prefix(),
2264 Command: irc.RPL_WHOISUSER,
2265 Params: []string{dc.nick, serviceNick, servicePrefix.User, servicePrefix.Host, "*", serviceRealname},
2266 })
2267 dc.SendMessage(&irc.Message{
2268 Prefix: dc.srv.prefix(),
2269 Command: irc.RPL_WHOISSERVER,
2270 Params: []string{dc.nick, serviceNick, dc.srv.Config().Hostname, "soju"},
2271 })
2272 dc.SendMessage(&irc.Message{
2273 Prefix: dc.srv.prefix(),
2274 Command: irc.RPL_WHOISOPERATOR,
2275 Params: []string{dc.nick, serviceNick, "is the bouncer service"},
2276 })
2277 dc.SendMessage(&irc.Message{
2278 Prefix: dc.srv.prefix(),
2279 Command: rpl_whoisaccount,
2280 Params: []string{dc.nick, serviceNick, serviceNick, "is logged in as"},
2281 })
2282 dc.SendMessage(&irc.Message{
2283 Prefix: dc.srv.prefix(),
2284 Command: irc.RPL_ENDOFWHOIS,
2285 Params: []string{dc.nick, serviceNick, "End of /WHOIS list"},
2286 })
2287 return nil
2288 }
2289
2290 // TODO: support WHOIS masks
2291 uc, upstreamNick, err := dc.unmarshalEntity(mask)
2292 if err != nil {
2293 return err
2294 }
2295
2296 var params []string
2297 if target != "" {
2298 if target == mask { // WHOIS nick nick
2299 params = []string{upstreamNick, upstreamNick}
2300 } else {
2301 params = []string{target, upstreamNick}
2302 }
2303 } else {
2304 params = []string{upstreamNick}
2305 }
2306
2307 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
2308 Command: "WHOIS",
2309 Params: params,
2310 })
2311 case "PRIVMSG", "NOTICE":
2312 var targetsStr, text string
2313 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
2314 return err
2315 }
2316 tags := copyClientTags(msg.Tags)
2317
2318 for _, name := range strings.Split(targetsStr, ",") {
2319 if name == "$"+dc.srv.Config().Hostname || (name == "$*" && dc.network == nil) {
2320 // "$" means a server mask follows. If it's the bouncer's
2321 // hostname, broadcast the message to all bouncer users.
2322 if !dc.user.Admin {
2323 return ircError{&irc.Message{
2324 Prefix: dc.srv.prefix(),
2325 Command: irc.ERR_BADMASK,
2326 Params: []string{dc.nick, name, "Permission denied to broadcast message to all bouncer users"},
2327 }}
2328 }
2329
2330 dc.logger.Printf("broadcasting bouncer-wide %v: %v", msg.Command, text)
2331
2332 broadcastTags := tags.Copy()
2333 broadcastTags["time"] = irc.TagValue(formatServerTime(time.Now()))
2334 broadcastMsg := &irc.Message{
2335 Tags: broadcastTags,
2336 Prefix: servicePrefix,
2337 Command: msg.Command,
2338 Params: []string{name, text},
2339 }
2340 dc.srv.forEachUser(func(u *user) {
2341 u.events <- eventBroadcast{broadcastMsg}
2342 })
2343 continue
2344 }
2345
2346 if dc.network == nil && casemapASCII(name) == dc.nickCM {
2347 dc.SendMessage(&irc.Message{
2348 Tags: msg.Tags.Copy(),
2349 Prefix: dc.prefix(),
2350 Command: msg.Command,
2351 Params: []string{name, text},
2352 })
2353 continue
2354 }
2355
2356 if msg.Command == "PRIVMSG" && casemapASCII(name) == serviceNickCM {
2357 if dc.caps["echo-message"] {
2358 echoTags := tags.Copy()
2359 echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
2360 dc.SendMessage(&irc.Message{
2361 Tags: echoTags,
2362 Prefix: dc.prefix(),
2363 Command: msg.Command,
2364 Params: []string{name, text},
2365 })
2366 }
2367 handleServicePRIVMSG(ctx, dc, text)
2368 continue
2369 }
2370
2371 uc, upstreamName, err := dc.unmarshalEntity(name)
2372 if err != nil {
2373 return err
2374 }
2375
2376 if msg.Command == "PRIVMSG" && uc.network.casemap(upstreamName) == "nickserv" {
2377 dc.handleNickServPRIVMSG(ctx, uc, text)
2378 }
2379
2380 unmarshaledText := text
2381 if uc.isChannel(upstreamName) {
2382 unmarshaledText = dc.unmarshalText(uc, text)
2383 }
2384 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
2385 Tags: tags,
2386 Command: msg.Command,
2387 Params: []string{upstreamName, unmarshaledText},
2388 })
2389
2390 echoTags := tags.Copy()
2391 echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
2392 if uc.account != "" {
2393 echoTags["account"] = irc.TagValue(uc.account)
2394 }
2395 echoMsg := &irc.Message{
2396 Tags: echoTags,
2397 Prefix: &irc.Prefix{Name: uc.nick},
2398 Command: msg.Command,
2399 Params: []string{upstreamName, text},
2400 }
2401 uc.produce(upstreamName, echoMsg, dc)
2402
2403 uc.updateChannelAutoDetach(upstreamName)
2404 }
2405 case "TAGMSG":
2406 var targetsStr string
2407 if err := parseMessageParams(msg, &targetsStr); err != nil {
2408 return err
2409 }
2410 tags := copyClientTags(msg.Tags)
2411
2412 for _, name := range strings.Split(targetsStr, ",") {
2413 if dc.network == nil && casemapASCII(name) == dc.nickCM {
2414 dc.SendMessage(&irc.Message{
2415 Tags: msg.Tags.Copy(),
2416 Prefix: dc.prefix(),
2417 Command: "TAGMSG",
2418 Params: []string{name},
2419 })
2420 continue
2421 }
2422
2423 if casemapASCII(name) == serviceNickCM {
2424 continue
2425 }
2426
2427 uc, upstreamName, err := dc.unmarshalEntity(name)
2428 if err != nil {
2429 return err
2430 }
2431 if _, ok := uc.caps["message-tags"]; !ok {
2432 continue
2433 }
2434
2435 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
2436 Tags: tags,
2437 Command: "TAGMSG",
2438 Params: []string{upstreamName},
2439 })
2440
2441 echoTags := tags.Copy()
2442 echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
2443 if uc.account != "" {
2444 echoTags["account"] = irc.TagValue(uc.account)
2445 }
2446 echoMsg := &irc.Message{
2447 Tags: echoTags,
2448 Prefix: &irc.Prefix{Name: uc.nick},
2449 Command: "TAGMSG",
2450 Params: []string{upstreamName},
2451 }
2452 uc.produce(upstreamName, echoMsg, dc)
2453
2454 uc.updateChannelAutoDetach(upstreamName)
2455 }
2456 case "INVITE":
2457 var user, channel string
2458 if err := parseMessageParams(msg, &user, &channel); err != nil {
2459 return err
2460 }
2461
2462 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
2463 if err != nil {
2464 return err
2465 }
2466
2467 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
2468 if err != nil {
2469 return err
2470 }
2471
2472 if ucChannel != ucUser {
2473 return ircError{&irc.Message{
2474 Command: irc.ERR_USERNOTINCHANNEL,
2475 Params: []string{dc.nick, user, channel, "They are on another network"},
2476 }}
2477 }
2478 uc := ucChannel
2479
2480 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
2481 Command: "INVITE",
2482 Params: []string{upstreamUser, upstreamChannel},
2483 })
2484 case "AUTHENTICATE":
2485 // Post-connection-registration AUTHENTICATE is unsupported in
2486 // multi-upstream mode, or if the upstream doesn't support SASL
2487 uc := dc.upstream()
2488 if uc == nil || !uc.caps["sasl"] {
2489 return ircError{&irc.Message{
2490 Command: irc.ERR_SASLFAIL,
2491 Params: []string{dc.nick, "Upstream network authentication not supported"},
2492 }}
2493 }
2494
2495 credentials, err := dc.handleAuthenticateCommand(msg)
2496 if err != nil {
2497 return err
2498 }
2499
2500 if credentials != nil {
2501 if uc.saslClient != nil {
2502 dc.endSASL(&irc.Message{
2503 Prefix: dc.srv.prefix(),
2504 Command: irc.ERR_SASLFAIL,
2505 Params: []string{dc.nick, "Another authentication attempt is already in progress"},
2506 })
2507 return nil
2508 }
2509
2510 uc.logger.Printf("starting post-registration SASL PLAIN authentication with username %q", credentials.plainUsername)
2511 uc.saslClient = sasl.NewPlainClient("", credentials.plainUsername, credentials.plainPassword)
2512 uc.enqueueCommand(dc, &irc.Message{
2513 Command: "AUTHENTICATE",
2514 Params: []string{"PLAIN"},
2515 })
2516 }
2517 case "REGISTER", "VERIFY":
2518 // Check number of params here, since we'll use that to save the
2519 // credentials on command success
2520 if (msg.Command == "REGISTER" && len(msg.Params) < 3) || (msg.Command == "VERIFY" && len(msg.Params) < 2) {
2521 return newNeedMoreParamsError(msg.Command)
2522 }
2523
2524 uc := dc.upstream()
2525 if uc == nil || !uc.caps["draft/account-registration"] {
2526 return ircError{&irc.Message{
2527 Command: "FAIL",
2528 Params: []string{msg.Command, "TEMPORARILY_UNAVAILABLE", "*", "Upstream network account registration not supported"},
2529 }}
2530 }
2531
2532 uc.logger.Printf("starting %v with account name %v", msg.Command, msg.Params[0])
2533 uc.enqueueCommand(dc, msg)
2534 case "MONITOR":
2535 // MONITOR is unsupported in multi-upstream mode
2536 uc := dc.upstream()
2537 if uc == nil {
2538 return newUnknownCommandError(msg.Command)
2539 }
2540 if _, ok := uc.isupport["MONITOR"]; !ok {
2541 return newUnknownCommandError(msg.Command)
2542 }
2543
2544 var subcommand string
2545 if err := parseMessageParams(msg, &subcommand); err != nil {
2546 return err
2547 }
2548
2549 switch strings.ToUpper(subcommand) {
2550 case "+", "-":
2551 var targets string
2552 if err := parseMessageParams(msg, nil, &targets); err != nil {
2553 return err
2554 }
2555 for _, target := range strings.Split(targets, ",") {
2556 if subcommand == "+" {
2557 // Hard limit, just to avoid having downstreams fill our map
2558 if len(dc.monitored.innerMap) >= 1000 {
2559 dc.SendMessage(&irc.Message{
2560 Prefix: dc.srv.prefix(),
2561 Command: irc.ERR_MONLISTFULL,
2562 Params: []string{dc.nick, "1000", target, "Bouncer monitor list is full"},
2563 })
2564 continue
2565 }
2566
2567 dc.monitored.SetValue(target, nil)
2568
2569 if uc.monitored.Has(target) {
2570 cmd := irc.RPL_MONOFFLINE
2571 if online := uc.monitored.Value(target); online {
2572 cmd = irc.RPL_MONONLINE
2573 }
2574
2575 dc.SendMessage(&irc.Message{
2576 Prefix: dc.srv.prefix(),
2577 Command: cmd,
2578 Params: []string{dc.nick, target},
2579 })
2580 }
2581 } else {
2582 dc.monitored.Delete(target)
2583 }
2584 }
2585 uc.updateMonitor()
2586 case "C": // clear
2587 dc.monitored = newCasemapMap(0)
2588 uc.updateMonitor()
2589 case "L": // list
2590 // TODO: be less lazy and pack the list
2591 for _, entry := range dc.monitored.innerMap {
2592 dc.SendMessage(&irc.Message{
2593 Prefix: dc.srv.prefix(),
2594 Command: irc.RPL_MONLIST,
2595 Params: []string{dc.nick, entry.originalKey},
2596 })
2597 }
2598 dc.SendMessage(&irc.Message{
2599 Prefix: dc.srv.prefix(),
2600 Command: irc.RPL_ENDOFMONLIST,
2601 Params: []string{dc.nick, "End of MONITOR list"},
2602 })
2603 case "S": // status
2604 // TODO: be less lazy and pack the lists
2605 for _, entry := range dc.monitored.innerMap {
2606 target := entry.originalKey
2607
2608 cmd := irc.RPL_MONOFFLINE
2609 if online := uc.monitored.Value(target); online {
2610 cmd = irc.RPL_MONONLINE
2611 }
2612
2613 dc.SendMessage(&irc.Message{
2614 Prefix: dc.srv.prefix(),
2615 Command: cmd,
2616 Params: []string{dc.nick, target},
2617 })
2618 }
2619 }
2620 case "CHATHISTORY":
2621 var subcommand string
2622 if err := parseMessageParams(msg, &subcommand); err != nil {
2623 return err
2624 }
2625 var target, limitStr string
2626 var boundsStr [2]string
2627 switch subcommand {
2628 case "AFTER", "BEFORE", "LATEST":
2629 if err := parseMessageParams(msg, nil, &target, &boundsStr[0], &limitStr); err != nil {
2630 return err
2631 }
2632 case "BETWEEN":
2633 if err := parseMessageParams(msg, nil, &target, &boundsStr[0], &boundsStr[1], &limitStr); err != nil {
2634 return err
2635 }
2636 case "TARGETS":
2637 if dc.network == nil {
2638 // Either an unbound bouncer network, in which case we should return no targets,
2639 // or a multi-upstream downstream, but we don't support CHATHISTORY TARGETS for those yet.
2640 dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef irc.TagValue) {})
2641 return nil
2642 }
2643 if err := parseMessageParams(msg, nil, &boundsStr[0], &boundsStr[1], &limitStr); err != nil {
2644 return err
2645 }
2646 default:
2647 // TODO: support AROUND
2648 return ircError{&irc.Message{
2649 Command: "FAIL",
2650 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, "Unknown command"},
2651 }}
2652 }
2653
2654 // We don't save history for our service
2655 if casemapASCII(target) == serviceNickCM {
2656 dc.SendBatch("chathistory", []string{target}, nil, func(batchRef irc.TagValue) {})
2657 return nil
2658 }
2659
2660 store, ok := dc.user.msgStore.(chatHistoryMessageStore)
2661 if !ok {
2662 return ircError{&irc.Message{
2663 Command: irc.ERR_UNKNOWNCOMMAND,
2664 Params: []string{dc.nick, "CHATHISTORY", "Unknown command"},
2665 }}
2666 }
2667
2668 network, entity, err := dc.unmarshalEntityNetwork(target)
2669 if err != nil {
2670 return err
2671 }
2672 entity = network.casemap(entity)
2673
2674 // TODO: support msgid criteria
2675 var bounds [2]time.Time
2676 bounds[0] = parseChatHistoryBound(boundsStr[0])
2677 if subcommand == "LATEST" && boundsStr[0] == "*" {
2678 bounds[0] = time.Now()
2679 } else if bounds[0].IsZero() {
2680 return ircError{&irc.Message{
2681 Command: "FAIL",
2682 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[0], "Invalid first bound"},
2683 }}
2684 }
2685
2686 if boundsStr[1] != "" {
2687 bounds[1] = parseChatHistoryBound(boundsStr[1])
2688 if bounds[1].IsZero() {
2689 return ircError{&irc.Message{
2690 Command: "FAIL",
2691 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[1], "Invalid second bound"},
2692 }}
2693 }
2694 }
2695
2696 limit, err := strconv.Atoi(limitStr)
2697 if err != nil || limit < 0 || limit > chatHistoryLimit {
2698 return ircError{&irc.Message{
2699 Command: "FAIL",
2700 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, limitStr, "Invalid limit"},
2701 }}
2702 }
2703
2704 eventPlayback := dc.caps["draft/event-playback"]
2705
2706 var history []*irc.Message
2707 switch subcommand {
2708 case "BEFORE", "LATEST":
2709 history, err = store.LoadBeforeTime(ctx, &network.Network, entity, bounds[0], time.Time{}, limit, eventPlayback)
2710 case "AFTER":
2711 history, err = store.LoadAfterTime(ctx, &network.Network, entity, bounds[0], time.Now(), limit, eventPlayback)
2712 case "BETWEEN":
2713 if bounds[0].Before(bounds[1]) {
2714 history, err = store.LoadAfterTime(ctx, &network.Network, entity, bounds[0], bounds[1], limit, eventPlayback)
2715 } else {
2716 history, err = store.LoadBeforeTime(ctx, &network.Network, entity, bounds[0], bounds[1], limit, eventPlayback)
2717 }
2718 case "TARGETS":
2719 // TODO: support TARGETS in multi-upstream mode
2720 targets, err := store.ListTargets(ctx, &network.Network, bounds[0], bounds[1], limit, eventPlayback)
2721 if err != nil {
2722 dc.logger.Printf("failed fetching targets for chathistory: %v", err)
2723 return ircError{&irc.Message{
2724 Command: "FAIL",
2725 Params: []string{"CHATHISTORY", "MESSAGE_ERROR", subcommand, "Failed to retrieve targets"},
2726 }}
2727 }
2728
2729 dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef irc.TagValue) {
2730 for _, target := range targets {
2731 if ch := network.channels.Value(target.Name); ch != nil && ch.Detached {
2732 continue
2733 }
2734
2735 dc.SendMessage(&irc.Message{
2736 Tags: irc.Tags{"batch": batchRef},
2737 Prefix: dc.srv.prefix(),
2738 Command: "CHATHISTORY",
2739 Params: []string{"TARGETS", target.Name, formatServerTime(target.LatestMessage)},
2740 })
2741 }
2742 })
2743
2744 return nil
2745 }
2746 if err != nil {
2747 dc.logger.Printf("failed fetching %q messages for chathistory: %v", target, err)
2748 return newChatHistoryError(subcommand, target)
2749 }
2750
2751 dc.SendBatch("chathistory", []string{target}, nil, func(batchRef irc.TagValue) {
2752 for _, msg := range history {
2753 msg.Tags["batch"] = batchRef
2754 dc.SendMessage(dc.marshalMessage(msg, network))
2755 }
2756 })
2757 case "READ":
2758 var target, criteria string
2759 if err := parseMessageParams(msg, &target); err != nil {
2760 return ircError{&irc.Message{
2761 Command: "FAIL",
2762 Params: []string{"READ", "NEED_MORE_PARAMS", "Missing parameters"},
2763 }}
2764 }
2765 if len(msg.Params) > 1 {
2766 criteria = msg.Params[1]
2767 }
2768
2769 // We don't save read receipts for our service
2770 if casemapASCII(target) == serviceNickCM {
2771 dc.SendMessage(&irc.Message{
2772 Prefix: dc.prefix(),
2773 Command: "READ",
2774 Params: []string{target, "*"},
2775 })
2776 return nil
2777 }
2778
2779 uc, entity, err := dc.unmarshalEntity(target)
2780 if err != nil {
2781 return err
2782 }
2783 entityCM := uc.network.casemap(entity)
2784
2785 r, err := dc.srv.db.GetReadReceipt(ctx, uc.network.ID, entityCM)
2786 if err != nil {
2787 dc.logger.Printf("failed to get the read receipt for %q: %v", entity, err)
2788 return ircError{&irc.Message{
2789 Command: "FAIL",
2790 Params: []string{"READ", "INTERNAL_ERROR", target, "Internal error"},
2791 }}
2792 } else if r == nil {
2793 r = &ReadReceipt{
2794 Target: entityCM,
2795 }
2796 }
2797
2798 broadcast := false
2799 if len(criteria) > 0 {
2800 // TODO: support msgid criteria
2801 criteriaParts := strings.SplitN(criteria, "=", 2)
2802 if len(criteriaParts) != 2 || criteriaParts[0] != "timestamp" {
2803 return ircError{&irc.Message{
2804 Command: "FAIL",
2805 Params: []string{"READ", "INVALID_PARAMS", criteria, "Unknown criteria"},
2806 }}
2807 }
2808
2809 timestamp, err := time.Parse(serverTimeLayout, criteriaParts[1])
2810 if err != nil {
2811 return ircError{&irc.Message{
2812 Command: "FAIL",
2813 Params: []string{"READ", "INVALID_PARAMS", criteria, "Invalid criteria"},
2814 }}
2815 }
2816 now := time.Now()
2817 if timestamp.After(now) {
2818 timestamp = now
2819 }
2820 if r.Timestamp.Before(timestamp) {
2821 r.Timestamp = timestamp
2822 if err := dc.srv.db.StoreReadReceipt(ctx, uc.network.ID, r); err != nil {
2823 dc.logger.Printf("failed to store receipt for %q: %v", entity, err)
2824 return ircError{&irc.Message{
2825 Command: "FAIL",
2826 Params: []string{"READ", "INTERNAL_ERROR", target, "Internal error"},
2827 }}
2828 }
2829 broadcast = true
2830 }
2831 }
2832
2833 timestampStr := "*"
2834 if !r.Timestamp.IsZero() {
2835 timestampStr = fmt.Sprintf("timestamp=%s", formatServerTime(r.Timestamp))
2836 }
2837 uc.forEachDownstream(func(d *downstreamConn) {
2838 if broadcast || dc.id == d.id {
2839 d.SendMessage(&irc.Message{
2840 Prefix: d.prefix(),
2841 Command: "READ",
2842 Params: []string{d.marshalEntity(uc.network, entity), timestampStr},
2843 })
2844 }
2845 })
2846 case "BOUNCER":
2847 var subcommand string
2848 if err := parseMessageParams(msg, &subcommand); err != nil {
2849 return err
2850 }
2851
2852 switch strings.ToUpper(subcommand) {
2853 case "BIND":
2854 return ircError{&irc.Message{
2855 Command: "FAIL",
2856 Params: []string{"BOUNCER", "REGISTRATION_IS_COMPLETED", "BIND", "Cannot bind to a network after registration"},
2857 }}
2858 case "LISTNETWORKS":
2859 dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
2860 for _, network := range dc.user.networks {
2861 idStr := fmt.Sprintf("%v", network.ID)
2862 attrs := getNetworkAttrs(network)
2863 dc.SendMessage(&irc.Message{
2864 Tags: irc.Tags{"batch": batchRef},
2865 Prefix: dc.srv.prefix(),
2866 Command: "BOUNCER",
2867 Params: []string{"NETWORK", idStr, attrs.String()},
2868 })
2869 }
2870 })
2871 case "ADDNETWORK":
2872 var attrsStr string
2873 if err := parseMessageParams(msg, nil, &attrsStr); err != nil {
2874 return err
2875 }
2876 attrs := irc.ParseTags(attrsStr)
2877
2878 record := &Network{Nick: dc.nick, Enabled: true}
2879 if err := updateNetworkAttrs(record, attrs, subcommand); err != nil {
2880 return err
2881 }
2882
2883 if record.Nick == dc.user.Username {
2884 record.Nick = ""
2885 }
2886 if record.Realname == dc.user.Realname {
2887 record.Realname = ""
2888 }
2889
2890 network, err := dc.user.createNetwork(ctx, record)
2891 if err != nil {
2892 return ircError{&irc.Message{
2893 Command: "FAIL",
2894 Params: []string{"BOUNCER", "UNKNOWN_ERROR", subcommand, fmt.Sprintf("Failed to create network: %v", err)},
2895 }}
2896 }
2897
2898 dc.SendMessage(&irc.Message{
2899 Prefix: dc.srv.prefix(),
2900 Command: "BOUNCER",
2901 Params: []string{"ADDNETWORK", fmt.Sprintf("%v", network.ID)},
2902 })
2903 case "CHANGENETWORK":
2904 var idStr, attrsStr string
2905 if err := parseMessageParams(msg, nil, &idStr, &attrsStr); err != nil {
2906 return err
2907 }
2908 id, err := parseBouncerNetID(subcommand, idStr)
2909 if err != nil {
2910 return err
2911 }
2912 attrs := irc.ParseTags(attrsStr)
2913
2914 net := dc.user.getNetworkByID(id)
2915 if net == nil {
2916 return ircError{&irc.Message{
2917 Command: "FAIL",
2918 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, idStr, "Invalid network ID"},
2919 }}
2920 }
2921
2922 record := net.Network // copy network record because we'll mutate it
2923 if err := updateNetworkAttrs(&record, attrs, subcommand); err != nil {
2924 return err
2925 }
2926
2927 if record.Nick == dc.user.Username {
2928 record.Nick = ""
2929 }
2930 if record.Realname == dc.user.Realname {
2931 record.Realname = ""
2932 }
2933
2934 _, err = dc.user.updateNetwork(ctx, &record)
2935 if err != nil {
2936 return ircError{&irc.Message{
2937 Command: "FAIL",
2938 Params: []string{"BOUNCER", "UNKNOWN_ERROR", subcommand, fmt.Sprintf("Failed to update network: %v", err)},
2939 }}
2940 }
2941
2942 dc.SendMessage(&irc.Message{
2943 Prefix: dc.srv.prefix(),
2944 Command: "BOUNCER",
2945 Params: []string{"CHANGENETWORK", idStr},
2946 })
2947 case "DELNETWORK":
2948 var idStr string
2949 if err := parseMessageParams(msg, nil, &idStr); err != nil {
2950 return err
2951 }
2952 id, err := parseBouncerNetID(subcommand, idStr)
2953 if err != nil {
2954 return err
2955 }
2956
2957 net := dc.user.getNetworkByID(id)
2958 if net == nil {
2959 return ircError{&irc.Message{
2960 Command: "FAIL",
2961 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, idStr, "Invalid network ID"},
2962 }}
2963 }
2964
2965 if err := dc.user.deleteNetwork(ctx, net.ID); err != nil {
2966 return err
2967 }
2968
2969 dc.SendMessage(&irc.Message{
2970 Prefix: dc.srv.prefix(),
2971 Command: "BOUNCER",
2972 Params: []string{"DELNETWORK", idStr},
2973 })
2974 default:
2975 return ircError{&irc.Message{
2976 Command: "FAIL",
2977 Params: []string{"BOUNCER", "UNKNOWN_COMMAND", subcommand, "Unknown subcommand"},
2978 }}
2979 }
2980 default:
2981 dc.logger.Printf("unhandled message: %v", msg)
2982
2983 // Only forward unknown commands in single-upstream mode
2984 uc := dc.upstream()
2985 if uc == nil {
2986 return newUnknownCommandError(msg.Command)
2987 }
2988
2989 uc.SendMessageLabeled(ctx, dc.id, msg)
2990 }
2991 return nil
2992}
2993
2994func (dc *downstreamConn) handleNickServPRIVMSG(ctx context.Context, uc *upstreamConn, text string) {
2995 username, password, ok := parseNickServCredentials(text, uc.nick)
2996 if ok {
2997 uc.network.autoSaveSASLPlain(ctx, username, password)
2998 }
2999}
3000
3001func parseNickServCredentials(text, nick string) (username, password string, ok bool) {
3002 fields := strings.Fields(text)
3003 if len(fields) < 2 {
3004 return "", "", false
3005 }
3006 cmd := strings.ToUpper(fields[0])
3007 params := fields[1:]
3008 switch cmd {
3009 case "REGISTER":
3010 username = nick
3011 password = params[0]
3012 case "IDENTIFY":
3013 if len(params) == 1 {
3014 username = nick
3015 password = params[0]
3016 } else {
3017 username = params[0]
3018 password = params[1]
3019 }
3020 case "SET":
3021 if len(params) == 2 && strings.EqualFold(params[0], "PASSWORD") {
3022 username = nick
3023 password = params[1]
3024 }
3025 default:
3026 return "", "", false
3027 }
3028 return username, password, true
3029}
Note: See TracBrowser for help on using the repository browser.