source: code/trunk/downstream.go@ 253

Last change on this file since 253 was 253, checked in by contact, 5 years ago

Per-entity ring buffers

Instead of having one ring buffer per network, each network has one ring
buffer per entity (channel or nick). This allows history to be more
fair: if there's a lot of activity in a channel, it won't prune activity
in other channels.

We now track history sequence numbers per client and per network in
networkHistory. The overall list of offline clients is still tracked in
network.offlineClients.

When all clients have received history, the ring buffer can be released.

In the future, we should get rid of too-old offline clients to avoid
having to maintain history for them forever. We should also add a
per-user limit on the number of ring buffers.

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