source: code/trunk/downstream.go@ 270

Last change on this file since 270 was 268, checked in by delthas, 5 years ago

Unmarshal nicks in texts of PRIVMSG and NOTICE from downstreams

When writing a PRIVMSG or NOTICE on a channel, it is very common to use
autocompletion to mention other users on that channel. When using soju
in multi-network mode, all users will have their nicked suffixed by
/network. This suffix should be removed before sending it upstream.

This adds support for removing all /network suffixes in messages sent
to a channel of that network.

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