source: code/trunk/downstream.go@ 657

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

Mark BouncerServ as server operator

That's what some widely used IRC servers do for their own services
(e.g. NickServ and ChanServ). This adds an additional level of
trust to make sure BouncerServ isn't typo'ed or impersonated.

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