source: code/trunk/downstream.go@ 654

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

Unify BOUNCER ADDNETWORK and CHANGENETWORK

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