source: code/trunk/downstream.go@ 708

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

Update downstream caps/nick/realname before sending MOTD

The MOTD indicates the end of the registration's message burst, and
the server can send arbitrary messages before it.

Update the supported capabilities, the nick and the realname before
MOTD to make it so client logic that runs on MOTD can work with
up-to-date info.

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