source: code/trunk/downstream.go@ 305

Last change on this file since 305 was 303, checked in by delthas, 5 years ago

Add support for TAGMSG and client message tags

Previously we dropped all TAGMSG as well as any client message tag sent
from downstream.

This adds support for properly forwarding TAGMSG and client message tags
from downstreams and upstreams.

TAGMSG messages are intentionally not logged, because they are currently
typically used for +typing, which can generate a lot of traffic and is
only useful for a few seconds after it is sent.

File size: 36.9 KB
Line 
1package soju
2
3import (
4 "crypto/tls"
5 "encoding/base64"
6 "fmt"
7 "io"
8 "net"
9 "strconv"
10 "strings"
11 "time"
12
13 "github.com/emersion/go-sasl"
14 "golang.org/x/crypto/bcrypt"
15 "gopkg.in/irc.v3"
16)
17
18type ircError struct {
19 Message *irc.Message
20}
21
22func (err ircError) Error() string {
23 return err.Message.String()
24}
25
26func newUnknownCommandError(cmd string) ircError {
27 return ircError{&irc.Message{
28 Command: irc.ERR_UNKNOWNCOMMAND,
29 Params: []string{
30 "*",
31 cmd,
32 "Unknown command",
33 },
34 }}
35}
36
37func newNeedMoreParamsError(cmd string) ircError {
38 return ircError{&irc.Message{
39 Command: irc.ERR_NEEDMOREPARAMS,
40 Params: []string{
41 "*",
42 cmd,
43 "Not enough parameters",
44 },
45 }}
46}
47
48var errAuthFailed = ircError{&irc.Message{
49 Command: irc.ERR_PASSWDMISMATCH,
50 Params: []string{"*", "Invalid username or password"},
51}}
52
53// permanentDownstreamCaps is the list of always-supported downstream
54// capabilities.
55var permanentDownstreamCaps = map[string]string{
56 "batch": "",
57 "cap-notify": "",
58 "echo-message": "",
59 "message-tags": "",
60 "sasl": "PLAIN",
61 "server-time": "",
62}
63
64// needAllDownstreamCaps is the list of downstream capabilities that
65// require support from all upstreams to be enabled
66var needAllDownstreamCaps = map[string]string{
67 "away-notify": "",
68 "multi-prefix": "",
69}
70
71type downstreamConn struct {
72 conn
73
74 id uint64
75
76 registered bool
77 user *user
78 nick string
79 rawUsername string
80 networkName string
81 clientName string
82 realname string
83 hostname string
84 password string // empty after authentication
85 network *network // can be nil
86
87 negociatingCaps bool
88 capVersion int
89 supportedCaps map[string]string
90 caps map[string]bool
91
92 saslServer sasl.Server
93}
94
95func newDownstreamConn(srv *Server, netConn net.Conn, id uint64) *downstreamConn {
96 logger := &prefixLogger{srv.Logger, fmt.Sprintf("downstream %q: ", netConn.RemoteAddr())}
97 dc := &downstreamConn{
98 conn: *newConn(srv, netConn, logger),
99 id: id,
100 supportedCaps: make(map[string]string),
101 caps: make(map[string]bool),
102 }
103 dc.hostname = netConn.RemoteAddr().String()
104 if host, _, err := net.SplitHostPort(dc.hostname); err == nil {
105 dc.hostname = host
106 }
107 for k, v := range permanentDownstreamCaps {
108 dc.supportedCaps[k] = v
109 }
110 return dc
111}
112
113func (dc *downstreamConn) prefix() *irc.Prefix {
114 return &irc.Prefix{
115 Name: dc.nick,
116 User: dc.user.Username,
117 Host: dc.hostname,
118 }
119}
120
121func (dc *downstreamConn) forEachNetwork(f func(*network)) {
122 if dc.network != nil {
123 f(dc.network)
124 } else {
125 dc.user.forEachNetwork(f)
126 }
127}
128
129func (dc *downstreamConn) forEachUpstream(f func(*upstreamConn)) {
130 dc.user.forEachUpstream(func(uc *upstreamConn) {
131 if dc.network != nil && uc.network != dc.network {
132 return
133 }
134 f(uc)
135 })
136}
137
138// upstream returns the upstream connection, if any. If there are zero or if
139// there are multiple upstream connections, it returns nil.
140func (dc *downstreamConn) upstream() *upstreamConn {
141 if dc.network == nil {
142 return nil
143 }
144 return dc.network.conn
145}
146
147func isOurNick(net *network, nick string) bool {
148 // TODO: this doesn't account for nick changes
149 if net.conn != nil {
150 return nick == net.conn.nick
151 }
152 // We're not currently connected to the upstream connection, so we don't
153 // know whether this name is our nickname. Best-effort: use the network's
154 // configured nickname and hope it was the one being used when we were
155 // connected.
156 return nick == net.Nick
157}
158
159// marshalEntity converts an upstream entity name (ie. channel or nick) into a
160// downstream entity name.
161//
162// This involves adding a "/<network>" suffix if the entity isn't the current
163// user.
164func (dc *downstreamConn) marshalEntity(net *network, name string) string {
165 if isOurNick(net, name) {
166 return dc.nick
167 }
168 if dc.network != nil {
169 if dc.network != net {
170 panic("soju: tried to marshal an entity for another network")
171 }
172 return name
173 }
174 return name + "/" + net.GetName()
175}
176
177func (dc *downstreamConn) marshalUserPrefix(net *network, prefix *irc.Prefix) *irc.Prefix {
178 if isOurNick(net, prefix.Name) {
179 return dc.prefix()
180 }
181 if dc.network != nil {
182 if dc.network != net {
183 panic("soju: tried to marshal a user prefix for another network")
184 }
185 return prefix
186 }
187 return &irc.Prefix{
188 Name: prefix.Name + "/" + net.GetName(),
189 User: prefix.User,
190 Host: prefix.Host,
191 }
192}
193
194// unmarshalEntity converts a downstream entity name (ie. channel or nick) into
195// an upstream entity name.
196//
197// This involves removing the "/<network>" suffix.
198func (dc *downstreamConn) unmarshalEntity(name string) (*upstreamConn, string, error) {
199 if uc := dc.upstream(); uc != nil {
200 return uc, name, nil
201 }
202
203 var conn *upstreamConn
204 if i := strings.LastIndexByte(name, '/'); i >= 0 {
205 network := name[i+1:]
206 name = name[:i]
207
208 dc.forEachUpstream(func(uc *upstreamConn) {
209 if network != uc.network.GetName() {
210 return
211 }
212 conn = uc
213 })
214 }
215
216 if conn == nil {
217 return nil, "", ircError{&irc.Message{
218 Command: irc.ERR_NOSUCHCHANNEL,
219 Params: []string{name, "No such channel"},
220 }}
221 }
222 return conn, name, nil
223}
224
225func (dc *downstreamConn) unmarshalText(uc *upstreamConn, text string) string {
226 if dc.upstream() != nil {
227 return text
228 }
229 // TODO: smarter parsing that ignores URLs
230 return strings.ReplaceAll(text, "/"+uc.network.GetName(), "")
231}
232
233func (dc *downstreamConn) readMessages(ch chan<- event) error {
234 for {
235 msg, err := dc.ReadMessage()
236 if err == io.EOF {
237 break
238 } else if err != nil {
239 return fmt.Errorf("failed to read IRC command: %v", err)
240 }
241
242 ch <- eventDownstreamMessage{msg, dc}
243 }
244
245 return nil
246}
247
248// SendMessage sends an outgoing message.
249//
250// This can only called from the user goroutine.
251func (dc *downstreamConn) SendMessage(msg *irc.Message) {
252 if !dc.caps["message-tags"] {
253 if msg.Command == "TAGMSG" {
254 return
255 }
256 msg = msg.Copy()
257 for name := range msg.Tags {
258 supported := false
259 switch name {
260 case "time":
261 supported = dc.caps["server-time"]
262 }
263 if !supported {
264 delete(msg.Tags, name)
265 }
266 }
267 }
268
269 dc.conn.SendMessage(msg)
270}
271
272// marshalMessage re-formats a message coming from an upstream connection so
273// that it's suitable for being sent on this downstream connection. Only
274// messages that may appear in logs are supported, except MODE.
275func (dc *downstreamConn) marshalMessage(msg *irc.Message, net *network) *irc.Message {
276 msg = msg.Copy()
277 msg.Prefix = dc.marshalUserPrefix(net, msg.Prefix)
278
279 switch msg.Command {
280 case "PRIVMSG", "NOTICE", "TAGMSG":
281 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
282 case "NICK":
283 // Nick change for another user
284 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
285 case "JOIN", "PART":
286 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
287 case "KICK":
288 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
289 msg.Params[1] = dc.marshalEntity(net, msg.Params[1])
290 case "TOPIC":
291 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
292 case "QUIT":
293 // This space is intentionally left blank
294 default:
295 panic(fmt.Sprintf("unexpected %q message", msg.Command))
296 }
297
298 return msg
299}
300
301func (dc *downstreamConn) handleMessage(msg *irc.Message) error {
302 switch msg.Command {
303 case "QUIT":
304 return dc.Close()
305 default:
306 if dc.registered {
307 return dc.handleMessageRegistered(msg)
308 } else {
309 return dc.handleMessageUnregistered(msg)
310 }
311 }
312}
313
314func (dc *downstreamConn) handleMessageUnregistered(msg *irc.Message) error {
315 switch msg.Command {
316 case "NICK":
317 var nick string
318 if err := parseMessageParams(msg, &nick); err != nil {
319 return err
320 }
321 if nick == serviceNick {
322 return ircError{&irc.Message{
323 Command: irc.ERR_NICKNAMEINUSE,
324 Params: []string{dc.nick, nick, "Nickname reserved for bouncer service"},
325 }}
326 }
327 dc.nick = nick
328 case "USER":
329 if err := parseMessageParams(msg, &dc.rawUsername, nil, nil, &dc.realname); err != nil {
330 return err
331 }
332 case "PASS":
333 if err := parseMessageParams(msg, &dc.password); err != nil {
334 return err
335 }
336 case "CAP":
337 var subCmd string
338 if err := parseMessageParams(msg, &subCmd); err != nil {
339 return err
340 }
341 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
342 return err
343 }
344 case "AUTHENTICATE":
345 if !dc.caps["sasl"] {
346 return ircError{&irc.Message{
347 Command: irc.ERR_SASLFAIL,
348 Params: []string{"*", "AUTHENTICATE requires the \"sasl\" capability to be enabled"},
349 }}
350 }
351 if len(msg.Params) == 0 {
352 return ircError{&irc.Message{
353 Command: irc.ERR_SASLFAIL,
354 Params: []string{"*", "Missing AUTHENTICATE argument"},
355 }}
356 }
357 if dc.nick == "" {
358 return ircError{&irc.Message{
359 Command: irc.ERR_SASLFAIL,
360 Params: []string{"*", "Expected NICK command before AUTHENTICATE"},
361 }}
362 }
363
364 var resp []byte
365 if dc.saslServer == nil {
366 mech := strings.ToUpper(msg.Params[0])
367 switch mech {
368 case "PLAIN":
369 dc.saslServer = sasl.NewPlainServer(sasl.PlainAuthenticator(func(identity, username, password string) error {
370 return dc.authenticate(username, password)
371 }))
372 default:
373 return ircError{&irc.Message{
374 Command: irc.ERR_SASLFAIL,
375 Params: []string{"*", fmt.Sprintf("Unsupported SASL mechanism %q", mech)},
376 }}
377 }
378 } else if msg.Params[0] == "*" {
379 dc.saslServer = nil
380 return ircError{&irc.Message{
381 Command: irc.ERR_SASLABORTED,
382 Params: []string{"*", "SASL authentication aborted"},
383 }}
384 } else if msg.Params[0] == "+" {
385 resp = nil
386 } else {
387 // TODO: multi-line messages
388 var err error
389 resp, err = base64.StdEncoding.DecodeString(msg.Params[0])
390 if err != nil {
391 dc.saslServer = nil
392 return ircError{&irc.Message{
393 Command: irc.ERR_SASLFAIL,
394 Params: []string{"*", "Invalid base64-encoded response"},
395 }}
396 }
397 }
398
399 challenge, done, err := dc.saslServer.Next(resp)
400 if err != nil {
401 dc.saslServer = nil
402 if ircErr, ok := err.(ircError); ok && ircErr.Message.Command == irc.ERR_PASSWDMISMATCH {
403 return ircError{&irc.Message{
404 Command: irc.ERR_SASLFAIL,
405 Params: []string{"*", ircErr.Message.Params[1]},
406 }}
407 }
408 dc.SendMessage(&irc.Message{
409 Prefix: dc.srv.prefix(),
410 Command: irc.ERR_SASLFAIL,
411 Params: []string{"*", "SASL error"},
412 })
413 return fmt.Errorf("SASL authentication failed: %v", err)
414 } else if done {
415 dc.saslServer = nil
416 dc.SendMessage(&irc.Message{
417 Prefix: dc.srv.prefix(),
418 Command: irc.RPL_LOGGEDIN,
419 Params: []string{dc.nick, dc.nick, dc.user.Username, "You are now logged in"},
420 })
421 dc.SendMessage(&irc.Message{
422 Prefix: dc.srv.prefix(),
423 Command: irc.RPL_SASLSUCCESS,
424 Params: []string{dc.nick, "SASL authentication successful"},
425 })
426 } else {
427 challengeStr := "+"
428 if len(challenge) > 0 {
429 challengeStr = base64.StdEncoding.EncodeToString(challenge)
430 }
431
432 // TODO: multi-line messages
433 dc.SendMessage(&irc.Message{
434 Prefix: dc.srv.prefix(),
435 Command: "AUTHENTICATE",
436 Params: []string{challengeStr},
437 })
438 }
439 default:
440 dc.logger.Printf("unhandled message: %v", msg)
441 return newUnknownCommandError(msg.Command)
442 }
443 if dc.rawUsername != "" && dc.nick != "" && !dc.negociatingCaps {
444 return dc.register()
445 }
446 return nil
447}
448
449func (dc *downstreamConn) handleCapCommand(cmd string, args []string) error {
450 cmd = strings.ToUpper(cmd)
451
452 replyTo := dc.nick
453 if !dc.registered {
454 replyTo = "*"
455 }
456
457 switch cmd {
458 case "LS":
459 if len(args) > 0 {
460 var err error
461 if dc.capVersion, err = strconv.Atoi(args[0]); err != nil {
462 return err
463 }
464 }
465
466 caps := make([]string, 0, len(dc.supportedCaps))
467 for k, v := range dc.supportedCaps {
468 if dc.capVersion >= 302 && v != "" {
469 caps = append(caps, k+"="+v)
470 } else {
471 caps = append(caps, k)
472 }
473 }
474
475 // TODO: multi-line replies
476 dc.SendMessage(&irc.Message{
477 Prefix: dc.srv.prefix(),
478 Command: "CAP",
479 Params: []string{replyTo, "LS", strings.Join(caps, " ")},
480 })
481
482 if dc.capVersion >= 302 {
483 // CAP version 302 implicitly enables cap-notify
484 dc.caps["cap-notify"] = true
485 }
486
487 if !dc.registered {
488 dc.negociatingCaps = true
489 }
490 case "LIST":
491 var caps []string
492 for name := range dc.caps {
493 caps = append(caps, name)
494 }
495
496 // TODO: multi-line replies
497 dc.SendMessage(&irc.Message{
498 Prefix: dc.srv.prefix(),
499 Command: "CAP",
500 Params: []string{replyTo, "LIST", strings.Join(caps, " ")},
501 })
502 case "REQ":
503 if len(args) == 0 {
504 return ircError{&irc.Message{
505 Command: err_invalidcapcmd,
506 Params: []string{replyTo, cmd, "Missing argument in CAP REQ command"},
507 }}
508 }
509
510 // TODO: atomically ack/nak the whole capability set
511 caps := strings.Fields(args[0])
512 ack := true
513 for _, name := range caps {
514 name = strings.ToLower(name)
515 enable := !strings.HasPrefix(name, "-")
516 if !enable {
517 name = strings.TrimPrefix(name, "-")
518 }
519
520 if enable == dc.caps[name] {
521 continue
522 }
523
524 _, ok := dc.supportedCaps[name]
525 if !ok {
526 ack = false
527 break
528 }
529
530 if name == "cap-notify" && dc.capVersion >= 302 && !enable {
531 // cap-notify cannot be disabled with CAP version 302
532 ack = false
533 break
534 }
535
536 dc.caps[name] = enable
537 }
538
539 reply := "NAK"
540 if ack {
541 reply = "ACK"
542 }
543 dc.SendMessage(&irc.Message{
544 Prefix: dc.srv.prefix(),
545 Command: "CAP",
546 Params: []string{replyTo, reply, args[0]},
547 })
548 case "END":
549 dc.negociatingCaps = false
550 default:
551 return ircError{&irc.Message{
552 Command: err_invalidcapcmd,
553 Params: []string{replyTo, cmd, "Unknown CAP command"},
554 }}
555 }
556 return nil
557}
558
559func (dc *downstreamConn) setSupportedCap(name, value string) {
560 prevValue, hasPrev := dc.supportedCaps[name]
561 changed := !hasPrev || prevValue != value
562 dc.supportedCaps[name] = value
563
564 if !dc.caps["cap-notify"] || !changed {
565 return
566 }
567
568 replyTo := dc.nick
569 if !dc.registered {
570 replyTo = "*"
571 }
572
573 cap := name
574 if value != "" && dc.capVersion >= 302 {
575 cap = name + "=" + value
576 }
577
578 dc.SendMessage(&irc.Message{
579 Prefix: dc.srv.prefix(),
580 Command: "CAP",
581 Params: []string{replyTo, "NEW", cap},
582 })
583}
584
585func (dc *downstreamConn) unsetSupportedCap(name string) {
586 _, hasPrev := dc.supportedCaps[name]
587 delete(dc.supportedCaps, name)
588 delete(dc.caps, name)
589
590 if !dc.caps["cap-notify"] || !hasPrev {
591 return
592 }
593
594 replyTo := dc.nick
595 if !dc.registered {
596 replyTo = "*"
597 }
598
599 dc.SendMessage(&irc.Message{
600 Prefix: dc.srv.prefix(),
601 Command: "CAP",
602 Params: []string{replyTo, "DEL", name},
603 })
604}
605
606func (dc *downstreamConn) updateSupportedCaps() {
607 supportedCaps := make(map[string]bool)
608 for cap := range needAllDownstreamCaps {
609 supportedCaps[cap] = true
610 }
611 dc.forEachUpstream(func(uc *upstreamConn) {
612 for cap, supported := range supportedCaps {
613 supportedCaps[cap] = supported && uc.caps[cap]
614 }
615 })
616
617 for cap, supported := range supportedCaps {
618 if supported {
619 dc.setSupportedCap(cap, needAllDownstreamCaps[cap])
620 } else {
621 dc.unsetSupportedCap(cap)
622 }
623 }
624}
625
626func (dc *downstreamConn) updateNick() {
627 if uc := dc.upstream(); uc != nil && uc.nick != dc.nick {
628 dc.SendMessage(&irc.Message{
629 Prefix: dc.prefix(),
630 Command: "NICK",
631 Params: []string{uc.nick},
632 })
633 dc.nick = uc.nick
634 }
635}
636
637func sanityCheckServer(addr string) error {
638 dialer := net.Dialer{Timeout: 30 * time.Second}
639 conn, err := tls.DialWithDialer(&dialer, "tcp", addr, nil)
640 if err != nil {
641 return err
642 }
643 return conn.Close()
644}
645
646func unmarshalUsername(rawUsername string) (username, client, network string) {
647 username = rawUsername
648
649 i := strings.IndexAny(username, "/@")
650 j := strings.LastIndexAny(username, "/@")
651 if i >= 0 {
652 username = rawUsername[:i]
653 }
654 if j >= 0 {
655 if rawUsername[j] == '@' {
656 client = rawUsername[j+1:]
657 } else {
658 network = rawUsername[j+1:]
659 }
660 }
661 if i >= 0 && j >= 0 && i < j {
662 if rawUsername[i] == '@' {
663 client = rawUsername[i+1 : j]
664 } else {
665 network = rawUsername[i+1 : j]
666 }
667 }
668
669 return username, client, network
670}
671
672func (dc *downstreamConn) authenticate(username, password string) error {
673 username, clientName, networkName := unmarshalUsername(username)
674
675 u, err := dc.srv.db.GetUser(username)
676 if err != nil {
677 dc.logger.Printf("failed authentication for %q: %v", username, err)
678 return errAuthFailed
679 }
680
681 err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
682 if err != nil {
683 dc.logger.Printf("failed authentication for %q: %v", username, err)
684 return errAuthFailed
685 }
686
687 dc.user = dc.srv.getUser(username)
688 if dc.user == nil {
689 dc.logger.Printf("failed authentication for %q: user not active", username)
690 return errAuthFailed
691 }
692 dc.clientName = clientName
693 dc.networkName = networkName
694 return nil
695}
696
697func (dc *downstreamConn) register() error {
698 if dc.registered {
699 return fmt.Errorf("tried to register twice")
700 }
701
702 password := dc.password
703 dc.password = ""
704 if dc.user == nil {
705 if err := dc.authenticate(dc.rawUsername, password); err != nil {
706 return err
707 }
708 }
709
710 if dc.clientName == "" && dc.networkName == "" {
711 _, dc.clientName, dc.networkName = unmarshalUsername(dc.rawUsername)
712 }
713
714 dc.registered = true
715 dc.logger.Printf("registration complete for user %q", dc.user.Username)
716 return nil
717}
718
719func (dc *downstreamConn) loadNetwork() error {
720 if dc.networkName == "" {
721 return nil
722 }
723
724 network := dc.user.getNetwork(dc.networkName)
725 if network == nil {
726 addr := dc.networkName
727 if !strings.ContainsRune(addr, ':') {
728 addr = addr + ":6697"
729 }
730
731 dc.logger.Printf("trying to connect to new network %q", addr)
732 if err := sanityCheckServer(addr); err != nil {
733 dc.logger.Printf("failed to connect to %q: %v", addr, err)
734 return ircError{&irc.Message{
735 Command: irc.ERR_PASSWDMISMATCH,
736 Params: []string{"*", fmt.Sprintf("Failed to connect to %q", dc.networkName)},
737 }}
738 }
739
740 dc.logger.Printf("auto-saving network %q", dc.networkName)
741 var err error
742 network, err = dc.user.createNetwork(&Network{
743 Addr: dc.networkName,
744 Nick: dc.nick,
745 })
746 if err != nil {
747 return err
748 }
749 }
750
751 dc.network = network
752 return nil
753}
754
755func (dc *downstreamConn) welcome() error {
756 if dc.user == nil || !dc.registered {
757 panic("tried to welcome an unregistered connection")
758 }
759
760 // TODO: doing this might take some time. We should do it in dc.register
761 // instead, but we'll potentially be adding a new network and this must be
762 // done in the user goroutine.
763 if err := dc.loadNetwork(); err != nil {
764 return err
765 }
766
767 dc.SendMessage(&irc.Message{
768 Prefix: dc.srv.prefix(),
769 Command: irc.RPL_WELCOME,
770 Params: []string{dc.nick, "Welcome to soju, " + dc.nick},
771 })
772 dc.SendMessage(&irc.Message{
773 Prefix: dc.srv.prefix(),
774 Command: irc.RPL_YOURHOST,
775 Params: []string{dc.nick, "Your host is " + dc.srv.Hostname},
776 })
777 dc.SendMessage(&irc.Message{
778 Prefix: dc.srv.prefix(),
779 Command: irc.RPL_CREATED,
780 Params: []string{dc.nick, "Who cares when the server was created?"},
781 })
782 dc.SendMessage(&irc.Message{
783 Prefix: dc.srv.prefix(),
784 Command: irc.RPL_MYINFO,
785 Params: []string{dc.nick, dc.srv.Hostname, "soju", "aiwroO", "OovaimnqpsrtklbeI"},
786 })
787 // TODO: RPL_ISUPPORT
788 dc.SendMessage(&irc.Message{
789 Prefix: dc.srv.prefix(),
790 Command: irc.ERR_NOMOTD,
791 Params: []string{dc.nick, "No MOTD"},
792 })
793
794 dc.updateNick()
795
796 dc.forEachUpstream(func(uc *upstreamConn) {
797 for _, ch := range uc.channels {
798 if !ch.complete {
799 continue
800 }
801 if record, ok := uc.network.channels[ch.Name]; ok && record.Detached {
802 continue
803 }
804
805 dc.SendMessage(&irc.Message{
806 Prefix: dc.prefix(),
807 Command: "JOIN",
808 Params: []string{dc.marshalEntity(ch.conn.network, ch.Name)},
809 })
810
811 forwardChannel(dc, ch)
812 }
813 })
814
815 dc.forEachNetwork(func(net *network) {
816 // Only send history if we're the first connected client with that name
817 // for the network
818 if _, ok := net.offlineClients[dc.clientName]; ok {
819 dc.sendNetworkHistory(net)
820 delete(net.offlineClients, dc.clientName)
821 }
822 })
823
824 return nil
825}
826
827func (dc *downstreamConn) sendNetworkHistory(net *network) {
828 for target, history := range net.history {
829 if ch, ok := net.channels[target]; ok && ch.Detached {
830 continue
831 }
832
833 seq, ok := history.offlineClients[dc.clientName]
834 if !ok {
835 continue
836 }
837 delete(history.offlineClients, dc.clientName)
838
839 // If all clients have received history, no need to keep the
840 // ring buffer around
841 if len(history.offlineClients) == 0 {
842 delete(net.history, target)
843 }
844
845 consumer := history.ring.NewConsumer(seq)
846
847 batchRef := "history"
848 if dc.caps["batch"] {
849 dc.SendMessage(&irc.Message{
850 Prefix: dc.srv.prefix(),
851 Command: "BATCH",
852 Params: []string{"+" + batchRef, "chathistory", dc.marshalEntity(net, target)},
853 })
854 }
855
856 for {
857 msg := consumer.Consume()
858 if msg == nil {
859 break
860 }
861
862 // Don't replay all messages, because that would mess up client
863 // state. For instance we just sent the list of users, sending
864 // PART messages for one of these users would be incorrect.
865 ignore := true
866 switch msg.Command {
867 case "PRIVMSG", "NOTICE":
868 ignore = false
869 }
870 if ignore {
871 continue
872 }
873
874 if dc.caps["batch"] {
875 msg = msg.Copy()
876 msg.Tags["batch"] = irc.TagValue(batchRef)
877 }
878
879 dc.SendMessage(dc.marshalMessage(msg, net))
880 }
881
882 if dc.caps["batch"] {
883 dc.SendMessage(&irc.Message{
884 Prefix: dc.srv.prefix(),
885 Command: "BATCH",
886 Params: []string{"-" + batchRef},
887 })
888 }
889 }
890}
891
892func (dc *downstreamConn) runUntilRegistered() error {
893 for !dc.registered {
894 msg, err := dc.ReadMessage()
895 if err != nil {
896 return fmt.Errorf("failed to read IRC command: %v", err)
897 }
898
899 err = dc.handleMessage(msg)
900 if ircErr, ok := err.(ircError); ok {
901 ircErr.Message.Prefix = dc.srv.prefix()
902 dc.SendMessage(ircErr.Message)
903 } else if err != nil {
904 return fmt.Errorf("failed to handle IRC command %q: %v", msg, err)
905 }
906 }
907
908 return nil
909}
910
911func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
912 switch msg.Command {
913 case "CAP":
914 var subCmd string
915 if err := parseMessageParams(msg, &subCmd); err != nil {
916 return err
917 }
918 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
919 return err
920 }
921 case "PING":
922 dc.SendMessage(&irc.Message{
923 Prefix: dc.srv.prefix(),
924 Command: "PONG",
925 Params: msg.Params,
926 })
927 return nil
928 case "USER":
929 return ircError{&irc.Message{
930 Command: irc.ERR_ALREADYREGISTERED,
931 Params: []string{dc.nick, "You may not reregister"},
932 }}
933 case "NICK":
934 var nick string
935 if err := parseMessageParams(msg, &nick); err != nil {
936 return err
937 }
938
939 var upstream *upstreamConn
940 if dc.upstream() == nil {
941 uc, unmarshaledNick, err := dc.unmarshalEntity(nick)
942 if err == nil { // NICK nick/network: NICK only on a specific upstream
943 upstream = uc
944 nick = unmarshaledNick
945 }
946 }
947
948 var err error
949 dc.forEachNetwork(func(n *network) {
950 if err != nil || (upstream != nil && upstream.network != n) {
951 return
952 }
953 n.Nick = nick
954 err = dc.srv.db.StoreNetwork(dc.user.Username, &n.Network)
955 })
956 if err != nil {
957 return err
958 }
959
960 dc.forEachUpstream(func(uc *upstreamConn) {
961 if upstream != nil && upstream != uc {
962 return
963 }
964 uc.SendMessageLabeled(dc.id, &irc.Message{
965 Command: "NICK",
966 Params: []string{nick},
967 })
968 })
969
970 if dc.upstream() == nil && dc.nick != nick {
971 dc.SendMessage(&irc.Message{
972 Prefix: dc.prefix(),
973 Command: "NICK",
974 Params: []string{nick},
975 })
976 dc.nick = nick
977 }
978 case "JOIN":
979 var namesStr string
980 if err := parseMessageParams(msg, &namesStr); err != nil {
981 return err
982 }
983
984 var keys []string
985 if len(msg.Params) > 1 {
986 keys = strings.Split(msg.Params[1], ",")
987 }
988
989 for i, name := range strings.Split(namesStr, ",") {
990 uc, upstreamName, err := dc.unmarshalEntity(name)
991 if err != nil {
992 return err
993 }
994
995 var key string
996 if len(keys) > i {
997 key = keys[i]
998 }
999
1000 params := []string{upstreamName}
1001 if key != "" {
1002 params = append(params, key)
1003 }
1004 uc.SendMessageLabeled(dc.id, &irc.Message{
1005 Command: "JOIN",
1006 Params: params,
1007 })
1008
1009 ch := &Channel{Name: upstreamName, Key: key, Detached: false}
1010 if current, ok := uc.network.channels[ch.Name]; ok && key == "" {
1011 // Don't clear the channel key if there's one set
1012 // TODO: add a way to unset the channel key
1013 ch.Key = current.Key
1014 }
1015 if err := uc.network.createUpdateChannel(ch); err != nil {
1016 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1017 }
1018 }
1019 case "PART":
1020 var namesStr string
1021 if err := parseMessageParams(msg, &namesStr); err != nil {
1022 return err
1023 }
1024
1025 var reason string
1026 if len(msg.Params) > 1 {
1027 reason = msg.Params[1]
1028 }
1029
1030 for _, name := range strings.Split(namesStr, ",") {
1031 uc, upstreamName, err := dc.unmarshalEntity(name)
1032 if err != nil {
1033 return err
1034 }
1035
1036 if strings.EqualFold(reason, "detach") {
1037 ch := &Channel{Name: upstreamName, Detached: true}
1038 if err := uc.network.createUpdateChannel(ch); err != nil {
1039 dc.logger.Printf("failed to detach channel %q: %v", upstreamName, err)
1040 }
1041 } else {
1042 params := []string{upstreamName}
1043 if reason != "" {
1044 params = append(params, reason)
1045 }
1046 uc.SendMessageLabeled(dc.id, &irc.Message{
1047 Command: "PART",
1048 Params: params,
1049 })
1050
1051 if err := uc.network.deleteChannel(upstreamName); err != nil {
1052 dc.logger.Printf("failed to delete channel %q: %v", upstreamName, err)
1053 }
1054 }
1055 }
1056 case "KICK":
1057 var channelStr, userStr string
1058 if err := parseMessageParams(msg, &channelStr, &userStr); err != nil {
1059 return err
1060 }
1061
1062 channels := strings.Split(channelStr, ",")
1063 users := strings.Split(userStr, ",")
1064
1065 var reason string
1066 if len(msg.Params) > 2 {
1067 reason = msg.Params[2]
1068 }
1069
1070 if len(channels) != 1 && len(channels) != len(users) {
1071 return ircError{&irc.Message{
1072 Command: irc.ERR_BADCHANMASK,
1073 Params: []string{dc.nick, channelStr, "Bad channel mask"},
1074 }}
1075 }
1076
1077 for i, user := range users {
1078 var channel string
1079 if len(channels) == 1 {
1080 channel = channels[0]
1081 } else {
1082 channel = channels[i]
1083 }
1084
1085 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1086 if err != nil {
1087 return err
1088 }
1089
1090 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1091 if err != nil {
1092 return err
1093 }
1094
1095 if ucChannel != ucUser {
1096 return ircError{&irc.Message{
1097 Command: irc.ERR_USERNOTINCHANNEL,
1098 Params: []string{dc.nick, user, channel, "They aren't on that channel"},
1099 }}
1100 }
1101 uc := ucChannel
1102
1103 params := []string{upstreamChannel, upstreamUser}
1104 if reason != "" {
1105 params = append(params, reason)
1106 }
1107 uc.SendMessageLabeled(dc.id, &irc.Message{
1108 Command: "KICK",
1109 Params: params,
1110 })
1111 }
1112 case "MODE":
1113 var name string
1114 if err := parseMessageParams(msg, &name); err != nil {
1115 return err
1116 }
1117
1118 var modeStr string
1119 if len(msg.Params) > 1 {
1120 modeStr = msg.Params[1]
1121 }
1122
1123 if name == dc.nick {
1124 if modeStr != "" {
1125 dc.forEachUpstream(func(uc *upstreamConn) {
1126 uc.SendMessageLabeled(dc.id, &irc.Message{
1127 Command: "MODE",
1128 Params: []string{uc.nick, modeStr},
1129 })
1130 })
1131 } else {
1132 dc.SendMessage(&irc.Message{
1133 Prefix: dc.srv.prefix(),
1134 Command: irc.RPL_UMODEIS,
1135 Params: []string{dc.nick, ""}, // TODO
1136 })
1137 }
1138 return nil
1139 }
1140
1141 uc, upstreamName, err := dc.unmarshalEntity(name)
1142 if err != nil {
1143 return err
1144 }
1145
1146 if !uc.isChannel(upstreamName) {
1147 return ircError{&irc.Message{
1148 Command: irc.ERR_USERSDONTMATCH,
1149 Params: []string{dc.nick, "Cannot change mode for other users"},
1150 }}
1151 }
1152
1153 if modeStr != "" {
1154 params := []string{upstreamName, modeStr}
1155 params = append(params, msg.Params[2:]...)
1156 uc.SendMessageLabeled(dc.id, &irc.Message{
1157 Command: "MODE",
1158 Params: params,
1159 })
1160 } else {
1161 ch, ok := uc.channels[upstreamName]
1162 if !ok {
1163 return ircError{&irc.Message{
1164 Command: irc.ERR_NOSUCHCHANNEL,
1165 Params: []string{dc.nick, name, "No such channel"},
1166 }}
1167 }
1168
1169 if ch.modes == nil {
1170 // we haven't received the initial RPL_CHANNELMODEIS yet
1171 // ignore the request, we will broadcast the modes later when we receive RPL_CHANNELMODEIS
1172 return nil
1173 }
1174
1175 modeStr, modeParams := ch.modes.Format()
1176 params := []string{dc.nick, name, modeStr}
1177 params = append(params, modeParams...)
1178
1179 dc.SendMessage(&irc.Message{
1180 Prefix: dc.srv.prefix(),
1181 Command: irc.RPL_CHANNELMODEIS,
1182 Params: params,
1183 })
1184 if ch.creationTime != "" {
1185 dc.SendMessage(&irc.Message{
1186 Prefix: dc.srv.prefix(),
1187 Command: rpl_creationtime,
1188 Params: []string{dc.nick, name, ch.creationTime},
1189 })
1190 }
1191 }
1192 case "TOPIC":
1193 var channel string
1194 if err := parseMessageParams(msg, &channel); err != nil {
1195 return err
1196 }
1197
1198 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1199 if err != nil {
1200 return err
1201 }
1202
1203 if len(msg.Params) > 1 { // setting topic
1204 topic := msg.Params[1]
1205 uc.SendMessageLabeled(dc.id, &irc.Message{
1206 Command: "TOPIC",
1207 Params: []string{upstreamChannel, topic},
1208 })
1209 } else { // getting topic
1210 ch, ok := uc.channels[upstreamChannel]
1211 if !ok {
1212 return ircError{&irc.Message{
1213 Command: irc.ERR_NOSUCHCHANNEL,
1214 Params: []string{dc.nick, upstreamChannel, "No such channel"},
1215 }}
1216 }
1217 sendTopic(dc, ch)
1218 }
1219 case "LIST":
1220 // TODO: support ELIST when supported by all upstreams
1221
1222 pl := pendingLIST{
1223 downstreamID: dc.id,
1224 pendingCommands: make(map[int64]*irc.Message),
1225 }
1226 var upstream *upstreamConn
1227 var upstreamChannels map[int64][]string
1228 if len(msg.Params) > 0 {
1229 uc, upstreamMask, err := dc.unmarshalEntity(msg.Params[0])
1230 if err == nil && upstreamMask == "*" { // LIST */network: send LIST only to one network
1231 upstream = uc
1232 } else {
1233 upstreamChannels = make(map[int64][]string)
1234 channels := strings.Split(msg.Params[0], ",")
1235 for _, channel := range channels {
1236 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1237 if err != nil {
1238 return err
1239 }
1240 upstreamChannels[uc.network.ID] = append(upstreamChannels[uc.network.ID], upstreamChannel)
1241 }
1242 }
1243 }
1244
1245 dc.user.pendingLISTs = append(dc.user.pendingLISTs, pl)
1246 dc.forEachUpstream(func(uc *upstreamConn) {
1247 if upstream != nil && upstream != uc {
1248 return
1249 }
1250 var params []string
1251 if upstreamChannels != nil {
1252 if channels, ok := upstreamChannels[uc.network.ID]; ok {
1253 params = []string{strings.Join(channels, ",")}
1254 } else {
1255 return
1256 }
1257 }
1258 pl.pendingCommands[uc.network.ID] = &irc.Message{
1259 Command: "LIST",
1260 Params: params,
1261 }
1262 uc.trySendLIST(dc.id)
1263 })
1264 case "NAMES":
1265 if len(msg.Params) == 0 {
1266 dc.SendMessage(&irc.Message{
1267 Prefix: dc.srv.prefix(),
1268 Command: irc.RPL_ENDOFNAMES,
1269 Params: []string{dc.nick, "*", "End of /NAMES list"},
1270 })
1271 return nil
1272 }
1273
1274 channels := strings.Split(msg.Params[0], ",")
1275 for _, channel := range channels {
1276 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1277 if err != nil {
1278 return err
1279 }
1280
1281 ch, ok := uc.channels[upstreamChannel]
1282 if ok {
1283 sendNames(dc, ch)
1284 } else {
1285 // NAMES on a channel we have not joined, ask upstream
1286 uc.SendMessageLabeled(dc.id, &irc.Message{
1287 Command: "NAMES",
1288 Params: []string{upstreamChannel},
1289 })
1290 }
1291 }
1292 case "WHO":
1293 if len(msg.Params) == 0 {
1294 // TODO: support WHO without parameters
1295 dc.SendMessage(&irc.Message{
1296 Prefix: dc.srv.prefix(),
1297 Command: irc.RPL_ENDOFWHO,
1298 Params: []string{dc.nick, "*", "End of /WHO list"},
1299 })
1300 return nil
1301 }
1302
1303 // TODO: support WHO masks
1304 entity := msg.Params[0]
1305
1306 if entity == dc.nick {
1307 // TODO: support AWAY (H/G) in self WHO reply
1308 dc.SendMessage(&irc.Message{
1309 Prefix: dc.srv.prefix(),
1310 Command: irc.RPL_WHOREPLY,
1311 Params: []string{dc.nick, "*", dc.user.Username, dc.hostname, dc.srv.Hostname, dc.nick, "H", "0 " + dc.realname},
1312 })
1313 dc.SendMessage(&irc.Message{
1314 Prefix: dc.srv.prefix(),
1315 Command: irc.RPL_ENDOFWHO,
1316 Params: []string{dc.nick, dc.nick, "End of /WHO list"},
1317 })
1318 return nil
1319 }
1320
1321 uc, upstreamName, err := dc.unmarshalEntity(entity)
1322 if err != nil {
1323 return err
1324 }
1325
1326 var params []string
1327 if len(msg.Params) == 2 {
1328 params = []string{upstreamName, msg.Params[1]}
1329 } else {
1330 params = []string{upstreamName}
1331 }
1332
1333 uc.SendMessageLabeled(dc.id, &irc.Message{
1334 Command: "WHO",
1335 Params: params,
1336 })
1337 case "WHOIS":
1338 if len(msg.Params) == 0 {
1339 return ircError{&irc.Message{
1340 Command: irc.ERR_NONICKNAMEGIVEN,
1341 Params: []string{dc.nick, "No nickname given"},
1342 }}
1343 }
1344
1345 var target, mask string
1346 if len(msg.Params) == 1 {
1347 target = ""
1348 mask = msg.Params[0]
1349 } else {
1350 target = msg.Params[0]
1351 mask = msg.Params[1]
1352 }
1353 // TODO: support multiple WHOIS users
1354 if i := strings.IndexByte(mask, ','); i >= 0 {
1355 mask = mask[:i]
1356 }
1357
1358 if mask == dc.nick {
1359 dc.SendMessage(&irc.Message{
1360 Prefix: dc.srv.prefix(),
1361 Command: irc.RPL_WHOISUSER,
1362 Params: []string{dc.nick, dc.nick, dc.user.Username, dc.hostname, "*", dc.realname},
1363 })
1364 dc.SendMessage(&irc.Message{
1365 Prefix: dc.srv.prefix(),
1366 Command: irc.RPL_WHOISSERVER,
1367 Params: []string{dc.nick, dc.nick, dc.srv.Hostname, "soju"},
1368 })
1369 dc.SendMessage(&irc.Message{
1370 Prefix: dc.srv.prefix(),
1371 Command: irc.RPL_ENDOFWHOIS,
1372 Params: []string{dc.nick, dc.nick, "End of /WHOIS list"},
1373 })
1374 return nil
1375 }
1376
1377 // TODO: support WHOIS masks
1378 uc, upstreamNick, err := dc.unmarshalEntity(mask)
1379 if err != nil {
1380 return err
1381 }
1382
1383 var params []string
1384 if target != "" {
1385 if target == mask { // WHOIS nick nick
1386 params = []string{upstreamNick, upstreamNick}
1387 } else {
1388 params = []string{target, upstreamNick}
1389 }
1390 } else {
1391 params = []string{upstreamNick}
1392 }
1393
1394 uc.SendMessageLabeled(dc.id, &irc.Message{
1395 Command: "WHOIS",
1396 Params: params,
1397 })
1398 case "PRIVMSG":
1399 var targetsStr, text string
1400 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1401 return err
1402 }
1403 tags := copyClientTags(msg.Tags)
1404
1405 for _, name := range strings.Split(targetsStr, ",") {
1406 if name == serviceNick {
1407 handleServicePRIVMSG(dc, text)
1408 continue
1409 }
1410
1411 uc, upstreamName, err := dc.unmarshalEntity(name)
1412 if err != nil {
1413 return err
1414 }
1415
1416 if upstreamName == "NickServ" {
1417 dc.handleNickServPRIVMSG(uc, text)
1418 }
1419
1420 unmarshaledText := text
1421 if uc.isChannel(upstreamName) {
1422 unmarshaledText = dc.unmarshalText(uc, text)
1423 }
1424 uc.SendMessageLabeled(dc.id, &irc.Message{
1425 Tags: tags,
1426 Command: "PRIVMSG",
1427 Params: []string{upstreamName, unmarshaledText},
1428 })
1429
1430 echoTags := tags.Copy()
1431 echoTags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
1432 echoMsg := &irc.Message{
1433 Tags: echoTags,
1434 Prefix: &irc.Prefix{
1435 Name: uc.nick,
1436 User: uc.username,
1437 },
1438 Command: "PRIVMSG",
1439 Params: []string{upstreamName, text},
1440 }
1441 uc.produce(upstreamName, echoMsg, dc)
1442 }
1443 case "NOTICE":
1444 var targetsStr, text string
1445 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1446 return err
1447 }
1448 tags := copyClientTags(msg.Tags)
1449
1450 for _, name := range strings.Split(targetsStr, ",") {
1451 uc, upstreamName, err := dc.unmarshalEntity(name)
1452 if err != nil {
1453 return err
1454 }
1455
1456 unmarshaledText := text
1457 if uc.isChannel(upstreamName) {
1458 unmarshaledText = dc.unmarshalText(uc, text)
1459 }
1460 uc.SendMessageLabeled(dc.id, &irc.Message{
1461 Tags: tags,
1462 Command: "NOTICE",
1463 Params: []string{upstreamName, unmarshaledText},
1464 })
1465 }
1466 case "TAGMSG":
1467 var targetsStr string
1468 if err := parseMessageParams(msg, &targetsStr); err != nil {
1469 return err
1470 }
1471 tags := copyClientTags(msg.Tags)
1472
1473 for _, name := range strings.Split(targetsStr, ",") {
1474 uc, upstreamName, err := dc.unmarshalEntity(name)
1475 if err != nil {
1476 return err
1477 }
1478
1479 uc.SendMessageLabeled(dc.id, &irc.Message{
1480 Tags: tags,
1481 Command: "TAGMSG",
1482 Params: []string{upstreamName},
1483 })
1484 }
1485 case "INVITE":
1486 var user, channel string
1487 if err := parseMessageParams(msg, &user, &channel); err != nil {
1488 return err
1489 }
1490
1491 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1492 if err != nil {
1493 return err
1494 }
1495
1496 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1497 if err != nil {
1498 return err
1499 }
1500
1501 if ucChannel != ucUser {
1502 return ircError{&irc.Message{
1503 Command: irc.ERR_USERNOTINCHANNEL,
1504 Params: []string{dc.nick, user, channel, "They aren't on that channel"},
1505 }}
1506 }
1507 uc := ucChannel
1508
1509 uc.SendMessageLabeled(dc.id, &irc.Message{
1510 Command: "INVITE",
1511 Params: []string{upstreamUser, upstreamChannel},
1512 })
1513 default:
1514 dc.logger.Printf("unhandled message: %v", msg)
1515 return newUnknownCommandError(msg.Command)
1516 }
1517 return nil
1518}
1519
1520func (dc *downstreamConn) handleNickServPRIVMSG(uc *upstreamConn, text string) {
1521 username, password, ok := parseNickServCredentials(text, uc.nick)
1522 if !ok {
1523 return
1524 }
1525
1526 dc.logger.Printf("auto-saving NickServ credentials with username %q", username)
1527 n := uc.network
1528 n.SASL.Mechanism = "PLAIN"
1529 n.SASL.Plain.Username = username
1530 n.SASL.Plain.Password = password
1531 if err := dc.srv.db.StoreNetwork(dc.user.Username, &n.Network); err != nil {
1532 dc.logger.Printf("failed to save NickServ credentials: %v", err)
1533 }
1534}
1535
1536func parseNickServCredentials(text, nick string) (username, password string, ok bool) {
1537 fields := strings.Fields(text)
1538 if len(fields) < 2 {
1539 return "", "", false
1540 }
1541 cmd := strings.ToUpper(fields[0])
1542 params := fields[1:]
1543 switch cmd {
1544 case "REGISTER":
1545 username = nick
1546 password = params[0]
1547 case "IDENTIFY":
1548 if len(params) == 1 {
1549 username = nick
1550 password = params[0]
1551 } else {
1552 username = params[0]
1553 password = params[1]
1554 }
1555 case "SET":
1556 if len(params) == 2 && strings.EqualFold(params[0], "PASSWORD") {
1557 username = nick
1558 password = params[1]
1559 }
1560 }
1561 return username, password, true
1562}
Note: See TracBrowser for help on using the repository browser.