source: code/trunk/downstream.go@ 295

Last change on this file since 295 was 293, checked in by delthas, 5 years ago

Fix parsing MODE messages by updating channel memberships

Previously, we only considered channel modes in the modes of a MODE
messages, which means channel membership changes were ignored. This
resulted in bugs where users channel memberships would not be properly
updated and cached with wrong values. Further, mode arguments
representing entities were not properly marshaled.

This adds support for correctly parsing and updating channel memberships
when processing MODE messages. Mode arguments corresponding to channel
memberships updates are now also properly marshaled.

MODE messages can't be easily sent from history because marshaling these
messages require knowing about the upstream available channel types and
channel membership types, which is currently only possible when
connected. For now this is not an issue since we do not send MODE
messages in history.

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