source: code/trunk/downstream.go@ 666

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

msgstore: take Network as arg instead of network

The message stores don't need to access the internal network
struct, they just need network metadata such as ID and name.

This can ease moving message stores into a separate package in the
future.

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