source: code/trunk/downstream.go@ 300

Last change on this file since 300 was 299, checked in by delthas, 5 years ago

Add support for downstream WHOIS nick/network nick/network

Many IRC clients use the query WHOIS nick nick rather than
WHOIS nick when querying a nick. The former command means to
specifically query the WHOIS on the server to which nick is connected,
which is useful to get information that is sometimes not propagated
between servers, such as idle time.

In the case where a downstream sends WHOIS nick/network nick/network in
multi-server mode, we need to unmarshal both fields.

Previously, we did not unmarshal those fields, and upstreams would
receive WHOIS nick/network nick, which is incorrect.

This adds support for unmarshaling the target field if it is the same as
the mask field, by simply using the unmarshaled nick that is already
computed from the mask.

File size: 36.1 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 (dc *downstreamConn) updateNick() {
624 if uc := dc.upstream(); uc != nil && uc.nick != dc.nick {
625 dc.SendMessage(&irc.Message{
626 Prefix: dc.prefix(),
627 Command: "NICK",
628 Params: []string{uc.nick},
629 })
630 dc.nick = uc.nick
631 }
632}
633
634func sanityCheckServer(addr string) error {
635 dialer := net.Dialer{Timeout: 30 * time.Second}
636 conn, err := tls.DialWithDialer(&dialer, "tcp", addr, nil)
637 if err != nil {
638 return err
639 }
640 return conn.Close()
641}
642
643func unmarshalUsername(rawUsername string) (username, client, network string) {
644 username = rawUsername
645
646 i := strings.IndexAny(username, "/@")
647 j := strings.LastIndexAny(username, "/@")
648 if i >= 0 {
649 username = rawUsername[:i]
650 }
651 if j >= 0 {
652 if rawUsername[j] == '@' {
653 client = rawUsername[j+1:]
654 } else {
655 network = rawUsername[j+1:]
656 }
657 }
658 if i >= 0 && j >= 0 && i < j {
659 if rawUsername[i] == '@' {
660 client = rawUsername[i+1 : j]
661 } else {
662 network = rawUsername[i+1 : j]
663 }
664 }
665
666 return username, client, network
667}
668
669func (dc *downstreamConn) authenticate(username, password string) error {
670 username, clientName, networkName := unmarshalUsername(username)
671
672 u, err := dc.srv.db.GetUser(username)
673 if err != nil {
674 dc.logger.Printf("failed authentication for %q: %v", username, err)
675 return errAuthFailed
676 }
677
678 err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
679 if err != nil {
680 dc.logger.Printf("failed authentication for %q: %v", username, err)
681 return errAuthFailed
682 }
683
684 dc.user = dc.srv.getUser(username)
685 if dc.user == nil {
686 dc.logger.Printf("failed authentication for %q: user not active", username)
687 return errAuthFailed
688 }
689 dc.clientName = clientName
690 dc.networkName = networkName
691 return nil
692}
693
694func (dc *downstreamConn) register() error {
695 if dc.registered {
696 return fmt.Errorf("tried to register twice")
697 }
698
699 password := dc.password
700 dc.password = ""
701 if dc.user == nil {
702 if err := dc.authenticate(dc.rawUsername, password); err != nil {
703 return err
704 }
705 }
706
707 if dc.clientName == "" && dc.networkName == "" {
708 _, dc.clientName, dc.networkName = unmarshalUsername(dc.rawUsername)
709 }
710
711 dc.registered = true
712 dc.logger.Printf("registration complete for user %q", dc.user.Username)
713 return nil
714}
715
716func (dc *downstreamConn) loadNetwork() error {
717 if dc.networkName == "" {
718 return nil
719 }
720
721 network := dc.user.getNetwork(dc.networkName)
722 if network == nil {
723 addr := dc.networkName
724 if !strings.ContainsRune(addr, ':') {
725 addr = addr + ":6697"
726 }
727
728 dc.logger.Printf("trying to connect to new network %q", addr)
729 if err := sanityCheckServer(addr); err != nil {
730 dc.logger.Printf("failed to connect to %q: %v", addr, err)
731 return ircError{&irc.Message{
732 Command: irc.ERR_PASSWDMISMATCH,
733 Params: []string{"*", fmt.Sprintf("Failed to connect to %q", dc.networkName)},
734 }}
735 }
736
737 dc.logger.Printf("auto-saving network %q", dc.networkName)
738 var err error
739 network, err = dc.user.createNetwork(&Network{
740 Addr: dc.networkName,
741 Nick: dc.nick,
742 })
743 if err != nil {
744 return err
745 }
746 }
747
748 dc.network = network
749 return nil
750}
751
752func (dc *downstreamConn) welcome() error {
753 if dc.user == nil || !dc.registered {
754 panic("tried to welcome an unregistered connection")
755 }
756
757 // TODO: doing this might take some time. We should do it in dc.register
758 // instead, but we'll potentially be adding a new network and this must be
759 // done in the user goroutine.
760 if err := dc.loadNetwork(); err != nil {
761 return err
762 }
763
764 dc.SendMessage(&irc.Message{
765 Prefix: dc.srv.prefix(),
766 Command: irc.RPL_WELCOME,
767 Params: []string{dc.nick, "Welcome to soju, " + dc.nick},
768 })
769 dc.SendMessage(&irc.Message{
770 Prefix: dc.srv.prefix(),
771 Command: irc.RPL_YOURHOST,
772 Params: []string{dc.nick, "Your host is " + dc.srv.Hostname},
773 })
774 dc.SendMessage(&irc.Message{
775 Prefix: dc.srv.prefix(),
776 Command: irc.RPL_CREATED,
777 Params: []string{dc.nick, "Who cares when the server was created?"},
778 })
779 dc.SendMessage(&irc.Message{
780 Prefix: dc.srv.prefix(),
781 Command: irc.RPL_MYINFO,
782 Params: []string{dc.nick, dc.srv.Hostname, "soju", "aiwroO", "OovaimnqpsrtklbeI"},
783 })
784 // TODO: RPL_ISUPPORT
785 dc.SendMessage(&irc.Message{
786 Prefix: dc.srv.prefix(),
787 Command: irc.ERR_NOMOTD,
788 Params: []string{dc.nick, "No MOTD"},
789 })
790
791 dc.updateNick()
792
793 dc.forEachUpstream(func(uc *upstreamConn) {
794 for _, ch := range uc.channels {
795 if !ch.complete {
796 continue
797 }
798 if record, ok := uc.network.channels[ch.Name]; ok && record.Detached {
799 continue
800 }
801
802 dc.SendMessage(&irc.Message{
803 Prefix: dc.prefix(),
804 Command: "JOIN",
805 Params: []string{dc.marshalEntity(ch.conn.network, ch.Name)},
806 })
807
808 forwardChannel(dc, ch)
809 }
810 })
811
812 dc.forEachNetwork(func(net *network) {
813 // Only send history if we're the first connected client with that name
814 // for the network
815 if _, ok := net.offlineClients[dc.clientName]; ok {
816 dc.sendNetworkHistory(net)
817 delete(net.offlineClients, dc.clientName)
818 }
819 })
820
821 return nil
822}
823
824func (dc *downstreamConn) sendNetworkHistory(net *network) {
825 for target, history := range net.history {
826 if ch, ok := net.channels[target]; ok && ch.Detached {
827 continue
828 }
829
830 seq, ok := history.offlineClients[dc.clientName]
831 if !ok {
832 continue
833 }
834 delete(history.offlineClients, dc.clientName)
835
836 // If all clients have received history, no need to keep the
837 // ring buffer around
838 if len(history.offlineClients) == 0 {
839 delete(net.history, target)
840 }
841
842 consumer := history.ring.NewConsumer(seq)
843
844 batchRef := "history"
845 if dc.caps["batch"] {
846 dc.SendMessage(&irc.Message{
847 Prefix: dc.srv.prefix(),
848 Command: "BATCH",
849 Params: []string{"+" + batchRef, "chathistory", dc.marshalEntity(net, target)},
850 })
851 }
852
853 for {
854 msg := consumer.Consume()
855 if msg == nil {
856 break
857 }
858
859 // Don't replay all messages, because that would mess up client
860 // state. For instance we just sent the list of users, sending
861 // PART messages for one of these users would be incorrect.
862 ignore := true
863 switch msg.Command {
864 case "PRIVMSG", "NOTICE":
865 ignore = false
866 }
867 if ignore {
868 continue
869 }
870
871 if dc.caps["batch"] {
872 msg = msg.Copy()
873 msg.Tags["batch"] = irc.TagValue(batchRef)
874 }
875
876 dc.SendMessage(dc.marshalMessage(msg, net))
877 }
878
879 if dc.caps["batch"] {
880 dc.SendMessage(&irc.Message{
881 Prefix: dc.srv.prefix(),
882 Command: "BATCH",
883 Params: []string{"-" + batchRef},
884 })
885 }
886 }
887}
888
889func (dc *downstreamConn) runUntilRegistered() error {
890 for !dc.registered {
891 msg, err := dc.ReadMessage()
892 if err != nil {
893 return fmt.Errorf("failed to read IRC command: %v", err)
894 }
895
896 err = dc.handleMessage(msg)
897 if ircErr, ok := err.(ircError); ok {
898 ircErr.Message.Prefix = dc.srv.prefix()
899 dc.SendMessage(ircErr.Message)
900 } else if err != nil {
901 return fmt.Errorf("failed to handle IRC command %q: %v", msg, err)
902 }
903 }
904
905 return nil
906}
907
908func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
909 switch msg.Command {
910 case "CAP":
911 var subCmd string
912 if err := parseMessageParams(msg, &subCmd); err != nil {
913 return err
914 }
915 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
916 return err
917 }
918 case "PING":
919 dc.SendMessage(&irc.Message{
920 Prefix: dc.srv.prefix(),
921 Command: "PONG",
922 Params: msg.Params,
923 })
924 return nil
925 case "USER":
926 return ircError{&irc.Message{
927 Command: irc.ERR_ALREADYREGISTERED,
928 Params: []string{dc.nick, "You may not reregister"},
929 }}
930 case "NICK":
931 var nick string
932 if err := parseMessageParams(msg, &nick); err != nil {
933 return err
934 }
935
936 var upstream *upstreamConn
937 if dc.upstream() == nil {
938 uc, unmarshaledNick, err := dc.unmarshalEntity(nick)
939 if err == nil { // NICK nick/network: NICK only on a specific upstream
940 upstream = uc
941 nick = unmarshaledNick
942 }
943 }
944
945 var err error
946 dc.forEachNetwork(func(n *network) {
947 if err != nil || (upstream != nil && upstream.network != n) {
948 return
949 }
950 n.Nick = nick
951 err = dc.srv.db.StoreNetwork(dc.user.Username, &n.Network)
952 })
953 if err != nil {
954 return err
955 }
956
957 dc.forEachUpstream(func(uc *upstreamConn) {
958 if upstream != nil && upstream != uc {
959 return
960 }
961 uc.SendMessage(&irc.Message{
962 Command: "NICK",
963 Params: []string{nick},
964 })
965 })
966
967 if dc.upstream() == nil && dc.nick != nick {
968 dc.SendMessage(&irc.Message{
969 Prefix: dc.prefix(),
970 Command: "NICK",
971 Params: []string{nick},
972 })
973 dc.nick = nick
974 }
975 case "JOIN":
976 var namesStr string
977 if err := parseMessageParams(msg, &namesStr); err != nil {
978 return err
979 }
980
981 var keys []string
982 if len(msg.Params) > 1 {
983 keys = strings.Split(msg.Params[1], ",")
984 }
985
986 for i, name := range strings.Split(namesStr, ",") {
987 uc, upstreamName, err := dc.unmarshalEntity(name)
988 if err != nil {
989 return err
990 }
991
992 var key string
993 if len(keys) > i {
994 key = keys[i]
995 }
996
997 params := []string{upstreamName}
998 if key != "" {
999 params = append(params, key)
1000 }
1001 uc.SendMessage(&irc.Message{
1002 Command: "JOIN",
1003 Params: params,
1004 })
1005
1006 ch := &Channel{Name: upstreamName, Key: key, Detached: false}
1007 if current, ok := uc.network.channels[ch.Name]; ok && key == "" {
1008 // Don't clear the channel key if there's one set
1009 // TODO: add a way to unset the channel key
1010 ch.Key = current.Key
1011 }
1012 if err := uc.network.createUpdateChannel(ch); err != nil {
1013 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1014 }
1015 }
1016 case "PART":
1017 var namesStr string
1018 if err := parseMessageParams(msg, &namesStr); err != nil {
1019 return err
1020 }
1021
1022 var reason string
1023 if len(msg.Params) > 1 {
1024 reason = msg.Params[1]
1025 }
1026
1027 for _, name := range strings.Split(namesStr, ",") {
1028 uc, upstreamName, err := dc.unmarshalEntity(name)
1029 if err != nil {
1030 return err
1031 }
1032
1033 if strings.EqualFold(reason, "detach") {
1034 ch := &Channel{Name: upstreamName, Detached: true}
1035 if err := uc.network.createUpdateChannel(ch); err != nil {
1036 dc.logger.Printf("failed to detach channel %q: %v", upstreamName, err)
1037 }
1038 } else {
1039 params := []string{upstreamName}
1040 if reason != "" {
1041 params = append(params, reason)
1042 }
1043 uc.SendMessage(&irc.Message{
1044 Command: "PART",
1045 Params: params,
1046 })
1047
1048 if err := uc.network.deleteChannel(upstreamName); err != nil {
1049 dc.logger.Printf("failed to delete channel %q: %v", upstreamName, err)
1050 }
1051 }
1052 }
1053 case "KICK":
1054 var channelStr, userStr string
1055 if err := parseMessageParams(msg, &channelStr, &userStr); err != nil {
1056 return err
1057 }
1058
1059 channels := strings.Split(channelStr, ",")
1060 users := strings.Split(userStr, ",")
1061
1062 var reason string
1063 if len(msg.Params) > 2 {
1064 reason = msg.Params[2]
1065 }
1066
1067 if len(channels) != 1 && len(channels) != len(users) {
1068 return ircError{&irc.Message{
1069 Command: irc.ERR_BADCHANMASK,
1070 Params: []string{dc.nick, channelStr, "Bad channel mask"},
1071 }}
1072 }
1073
1074 for i, user := range users {
1075 var channel string
1076 if len(channels) == 1 {
1077 channel = channels[0]
1078 } else {
1079 channel = channels[i]
1080 }
1081
1082 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1083 if err != nil {
1084 return err
1085 }
1086
1087 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1088 if err != nil {
1089 return err
1090 }
1091
1092 if ucChannel != ucUser {
1093 return ircError{&irc.Message{
1094 Command: irc.ERR_USERNOTINCHANNEL,
1095 Params: []string{dc.nick, user, channel, "They aren't on that channel"},
1096 }}
1097 }
1098 uc := ucChannel
1099
1100 params := []string{upstreamChannel, upstreamUser}
1101 if reason != "" {
1102 params = append(params, reason)
1103 }
1104 uc.SendMessage(&irc.Message{
1105 Command: "KICK",
1106 Params: params,
1107 })
1108 }
1109 case "MODE":
1110 var name string
1111 if err := parseMessageParams(msg, &name); err != nil {
1112 return err
1113 }
1114
1115 var modeStr string
1116 if len(msg.Params) > 1 {
1117 modeStr = msg.Params[1]
1118 }
1119
1120 if name == dc.nick {
1121 if modeStr != "" {
1122 dc.forEachUpstream(func(uc *upstreamConn) {
1123 uc.SendMessage(&irc.Message{
1124 Command: "MODE",
1125 Params: []string{uc.nick, modeStr},
1126 })
1127 })
1128 } else {
1129 dc.SendMessage(&irc.Message{
1130 Prefix: dc.srv.prefix(),
1131 Command: irc.RPL_UMODEIS,
1132 Params: []string{dc.nick, ""}, // TODO
1133 })
1134 }
1135 return nil
1136 }
1137
1138 uc, upstreamName, err := dc.unmarshalEntity(name)
1139 if err != nil {
1140 return err
1141 }
1142
1143 if !uc.isChannel(upstreamName) {
1144 return ircError{&irc.Message{
1145 Command: irc.ERR_USERSDONTMATCH,
1146 Params: []string{dc.nick, "Cannot change mode for other users"},
1147 }}
1148 }
1149
1150 if modeStr != "" {
1151 params := []string{upstreamName, modeStr}
1152 params = append(params, msg.Params[2:]...)
1153 uc.SendMessage(&irc.Message{
1154 Command: "MODE",
1155 Params: params,
1156 })
1157 } else {
1158 ch, ok := uc.channels[upstreamName]
1159 if !ok {
1160 return ircError{&irc.Message{
1161 Command: irc.ERR_NOSUCHCHANNEL,
1162 Params: []string{dc.nick, name, "No such channel"},
1163 }}
1164 }
1165
1166 if ch.modes == nil {
1167 // we haven't received the initial RPL_CHANNELMODEIS yet
1168 // ignore the request, we will broadcast the modes later when we receive RPL_CHANNELMODEIS
1169 return nil
1170 }
1171
1172 modeStr, modeParams := ch.modes.Format()
1173 params := []string{dc.nick, name, modeStr}
1174 params = append(params, modeParams...)
1175
1176 dc.SendMessage(&irc.Message{
1177 Prefix: dc.srv.prefix(),
1178 Command: irc.RPL_CHANNELMODEIS,
1179 Params: params,
1180 })
1181 if ch.creationTime != "" {
1182 dc.SendMessage(&irc.Message{
1183 Prefix: dc.srv.prefix(),
1184 Command: rpl_creationtime,
1185 Params: []string{dc.nick, name, ch.creationTime},
1186 })
1187 }
1188 }
1189 case "TOPIC":
1190 var channel string
1191 if err := parseMessageParams(msg, &channel); err != nil {
1192 return err
1193 }
1194
1195 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1196 if err != nil {
1197 return err
1198 }
1199
1200 if len(msg.Params) > 1 { // setting topic
1201 topic := msg.Params[1]
1202 uc.SendMessage(&irc.Message{
1203 Command: "TOPIC",
1204 Params: []string{upstreamChannel, topic},
1205 })
1206 } else { // getting topic
1207 ch, ok := uc.channels[upstreamChannel]
1208 if !ok {
1209 return ircError{&irc.Message{
1210 Command: irc.ERR_NOSUCHCHANNEL,
1211 Params: []string{dc.nick, upstreamChannel, "No such channel"},
1212 }}
1213 }
1214 sendTopic(dc, ch)
1215 }
1216 case "LIST":
1217 // TODO: support ELIST when supported by all upstreams
1218
1219 pl := pendingLIST{
1220 downstreamID: dc.id,
1221 pendingCommands: make(map[int64]*irc.Message),
1222 }
1223 var upstream *upstreamConn
1224 var upstreamChannels map[int64][]string
1225 if len(msg.Params) > 0 {
1226 uc, upstreamMask, err := dc.unmarshalEntity(msg.Params[0])
1227 if err == nil && upstreamMask == "*" { // LIST */network: send LIST only to one network
1228 upstream = uc
1229 } else {
1230 upstreamChannels = make(map[int64][]string)
1231 channels := strings.Split(msg.Params[0], ",")
1232 for _, channel := range channels {
1233 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1234 if err != nil {
1235 return err
1236 }
1237 upstreamChannels[uc.network.ID] = append(upstreamChannels[uc.network.ID], upstreamChannel)
1238 }
1239 }
1240 }
1241
1242 dc.user.pendingLISTs = append(dc.user.pendingLISTs, pl)
1243 dc.forEachUpstream(func(uc *upstreamConn) {
1244 if upstream != nil && upstream != uc {
1245 return
1246 }
1247 var params []string
1248 if upstreamChannels != nil {
1249 if channels, ok := upstreamChannels[uc.network.ID]; ok {
1250 params = []string{strings.Join(channels, ",")}
1251 } else {
1252 return
1253 }
1254 }
1255 pl.pendingCommands[uc.network.ID] = &irc.Message{
1256 Command: "LIST",
1257 Params: params,
1258 }
1259 uc.trySendLIST(dc.id)
1260 })
1261 case "NAMES":
1262 if len(msg.Params) == 0 {
1263 dc.SendMessage(&irc.Message{
1264 Prefix: dc.srv.prefix(),
1265 Command: irc.RPL_ENDOFNAMES,
1266 Params: []string{dc.nick, "*", "End of /NAMES list"},
1267 })
1268 return nil
1269 }
1270
1271 channels := strings.Split(msg.Params[0], ",")
1272 for _, channel := range channels {
1273 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1274 if err != nil {
1275 return err
1276 }
1277
1278 ch, ok := uc.channels[upstreamChannel]
1279 if ok {
1280 sendNames(dc, ch)
1281 } else {
1282 // NAMES on a channel we have not joined, ask upstream
1283 uc.SendMessageLabeled(dc.id, &irc.Message{
1284 Command: "NAMES",
1285 Params: []string{upstreamChannel},
1286 })
1287 }
1288 }
1289 case "WHO":
1290 if len(msg.Params) == 0 {
1291 // TODO: support WHO without parameters
1292 dc.SendMessage(&irc.Message{
1293 Prefix: dc.srv.prefix(),
1294 Command: irc.RPL_ENDOFWHO,
1295 Params: []string{dc.nick, "*", "End of /WHO list"},
1296 })
1297 return nil
1298 }
1299
1300 // TODO: support WHO masks
1301 entity := msg.Params[0]
1302
1303 if entity == dc.nick {
1304 // TODO: support AWAY (H/G) in self WHO reply
1305 dc.SendMessage(&irc.Message{
1306 Prefix: dc.srv.prefix(),
1307 Command: irc.RPL_WHOREPLY,
1308 Params: []string{dc.nick, "*", dc.user.Username, dc.hostname, dc.srv.Hostname, dc.nick, "H", "0 " + dc.realname},
1309 })
1310 dc.SendMessage(&irc.Message{
1311 Prefix: dc.srv.prefix(),
1312 Command: irc.RPL_ENDOFWHO,
1313 Params: []string{dc.nick, dc.nick, "End of /WHO list"},
1314 })
1315 return nil
1316 }
1317
1318 uc, upstreamName, err := dc.unmarshalEntity(entity)
1319 if err != nil {
1320 return err
1321 }
1322
1323 var params []string
1324 if len(msg.Params) == 2 {
1325 params = []string{upstreamName, msg.Params[1]}
1326 } else {
1327 params = []string{upstreamName}
1328 }
1329
1330 uc.SendMessageLabeled(dc.id, &irc.Message{
1331 Command: "WHO",
1332 Params: params,
1333 })
1334 case "WHOIS":
1335 if len(msg.Params) == 0 {
1336 return ircError{&irc.Message{
1337 Command: irc.ERR_NONICKNAMEGIVEN,
1338 Params: []string{dc.nick, "No nickname given"},
1339 }}
1340 }
1341
1342 var target, mask string
1343 if len(msg.Params) == 1 {
1344 target = ""
1345 mask = msg.Params[0]
1346 } else {
1347 target = msg.Params[0]
1348 mask = msg.Params[1]
1349 }
1350 // TODO: support multiple WHOIS users
1351 if i := strings.IndexByte(mask, ','); i >= 0 {
1352 mask = mask[:i]
1353 }
1354
1355 if mask == dc.nick {
1356 dc.SendMessage(&irc.Message{
1357 Prefix: dc.srv.prefix(),
1358 Command: irc.RPL_WHOISUSER,
1359 Params: []string{dc.nick, dc.nick, dc.user.Username, dc.hostname, "*", dc.realname},
1360 })
1361 dc.SendMessage(&irc.Message{
1362 Prefix: dc.srv.prefix(),
1363 Command: irc.RPL_WHOISSERVER,
1364 Params: []string{dc.nick, dc.nick, dc.srv.Hostname, "soju"},
1365 })
1366 dc.SendMessage(&irc.Message{
1367 Prefix: dc.srv.prefix(),
1368 Command: irc.RPL_ENDOFWHOIS,
1369 Params: []string{dc.nick, dc.nick, "End of /WHOIS list"},
1370 })
1371 return nil
1372 }
1373
1374 // TODO: support WHOIS masks
1375 uc, upstreamNick, err := dc.unmarshalEntity(mask)
1376 if err != nil {
1377 return err
1378 }
1379
1380 var params []string
1381 if target != "" {
1382 if target == mask { // WHOIS nick nick
1383 params = []string{upstreamNick, upstreamNick}
1384 } else {
1385 params = []string{target, upstreamNick}
1386 }
1387 } else {
1388 params = []string{upstreamNick}
1389 }
1390
1391 uc.SendMessageLabeled(dc.id, &irc.Message{
1392 Command: "WHOIS",
1393 Params: params,
1394 })
1395 case "PRIVMSG":
1396 var targetsStr, text string
1397 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1398 return err
1399 }
1400
1401 for _, name := range strings.Split(targetsStr, ",") {
1402 if name == serviceNick {
1403 handleServicePRIVMSG(dc, text)
1404 continue
1405 }
1406
1407 uc, upstreamName, err := dc.unmarshalEntity(name)
1408 if err != nil {
1409 return err
1410 }
1411
1412 if upstreamName == "NickServ" {
1413 dc.handleNickServPRIVMSG(uc, text)
1414 }
1415
1416 unmarshaledText := text
1417 if uc.isChannel(upstreamName) {
1418 unmarshaledText = dc.unmarshalText(uc, text)
1419 }
1420 uc.SendMessage(&irc.Message{
1421 Command: "PRIVMSG",
1422 Params: []string{upstreamName, unmarshaledText},
1423 })
1424
1425 echoMsg := &irc.Message{
1426 Tags: irc.Tags{
1427 "time": irc.TagValue(time.Now().UTC().Format(serverTimeLayout)),
1428 },
1429 Prefix: &irc.Prefix{
1430 Name: uc.nick,
1431 User: uc.username,
1432 },
1433 Command: "PRIVMSG",
1434 Params: []string{upstreamName, text},
1435 }
1436 uc.produce(upstreamName, echoMsg, dc)
1437 }
1438 case "NOTICE":
1439 var targetsStr, text string
1440 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1441 return err
1442 }
1443
1444 for _, name := range strings.Split(targetsStr, ",") {
1445 uc, upstreamName, err := dc.unmarshalEntity(name)
1446 if err != nil {
1447 return err
1448 }
1449
1450 unmarshaledText := text
1451 if uc.isChannel(upstreamName) {
1452 unmarshaledText = dc.unmarshalText(uc, text)
1453 }
1454 uc.SendMessage(&irc.Message{
1455 Command: "NOTICE",
1456 Params: []string{upstreamName, unmarshaledText},
1457 })
1458 }
1459 case "INVITE":
1460 var user, channel string
1461 if err := parseMessageParams(msg, &user, &channel); err != nil {
1462 return err
1463 }
1464
1465 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1466 if err != nil {
1467 return err
1468 }
1469
1470 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1471 if err != nil {
1472 return err
1473 }
1474
1475 if ucChannel != ucUser {
1476 return ircError{&irc.Message{
1477 Command: irc.ERR_USERNOTINCHANNEL,
1478 Params: []string{dc.nick, user, channel, "They aren't on that channel"},
1479 }}
1480 }
1481 uc := ucChannel
1482
1483 uc.SendMessageLabeled(dc.id, &irc.Message{
1484 Command: "INVITE",
1485 Params: []string{upstreamUser, upstreamChannel},
1486 })
1487 default:
1488 dc.logger.Printf("unhandled message: %v", msg)
1489 return newUnknownCommandError(msg.Command)
1490 }
1491 return nil
1492}
1493
1494func (dc *downstreamConn) handleNickServPRIVMSG(uc *upstreamConn, text string) {
1495 username, password, ok := parseNickServCredentials(text, uc.nick)
1496 if !ok {
1497 return
1498 }
1499
1500 dc.logger.Printf("auto-saving NickServ credentials with username %q", username)
1501 n := uc.network
1502 n.SASL.Mechanism = "PLAIN"
1503 n.SASL.Plain.Username = username
1504 n.SASL.Plain.Password = password
1505 if err := dc.srv.db.StoreNetwork(dc.user.Username, &n.Network); err != nil {
1506 dc.logger.Printf("failed to save NickServ credentials: %v", err)
1507 }
1508}
1509
1510func parseNickServCredentials(text, nick string) (username, password string, ok bool) {
1511 fields := strings.Fields(text)
1512 if len(fields) < 2 {
1513 return "", "", false
1514 }
1515 cmd := strings.ToUpper(fields[0])
1516 params := fields[1:]
1517 switch cmd {
1518 case "REGISTER":
1519 username = nick
1520 password = params[0]
1521 case "IDENTIFY":
1522 if len(params) == 1 {
1523 username = nick
1524 password = params[0]
1525 } else {
1526 username = params[0]
1527 password = params[1]
1528 }
1529 case "SET":
1530 if len(params) == 2 && strings.EqualFold(params[0], "PASSWORD") {
1531 username = nick
1532 password = params[1]
1533 }
1534 }
1535 return username, password, true
1536}
Note: See TracBrowser for help on using the repository browser.