source: code/trunk/downstream.go@ 260

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

Make downstreamConn.marshal{Entity,UserPrefix} take a network

This will be used when sending history while upstream is disconnected.

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