source: code/trunk/downstream.go@ 769

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

Drop user.forEachNetwork

It's a trivial for loop.

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