source: code/trunk/downstream.go@ 684

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

Add support for MONITOR

Add support for MONITOR in single-upstream mode.

Each downstream has its own set of monitored targets. These sets
are merged together to compute the MONITOR commands to send to
upstream.

Each upstream has a set of monitored targets accepted by the server
alongside with their status (online/offline). This is used to
directly send replies to downstreams adding a target another
downstream has already added, and send MONITOR S[TATUS] replies.

Co-authored-by: delthas <delthas@…>

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