source: code/trunk/downstream.go@ 652

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

Add context args to Database interface

This is a mecanical change, which just lifts up the context.TODO()
calls from inside the DB implementations to the callers.

Future work involves properly wiring up the contexts when it makes
sense.

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