source: code/trunk/downstream.go@ 420

Last change on this file since 420 was 419, checked in by delthas, 5 years ago

Add support for the extended-join capability

This simple implementation only advertises extended-join to downstreams
when all upstreams support it.

In the future, it could be modified so that soju buffers incoming
upstream JOINs, sends a WHO, waits for the reply, and sends an extended
join to the downstream; so that soju could advertise that capability
even when some or all upstreams do not support it. This is not the case
in this commit.

File size: 42.0 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
48func newChatHistoryError(subcommand string, target string) ircError {
49 return ircError{&irc.Message{
50 Command: "FAIL",
51 Params: []string{"CHATHISTORY", "MESSAGE_ERROR", subcommand, target, "Messages could not be retrieved"},
52 }}
53}
54
55var errAuthFailed = ircError{&irc.Message{
56 Command: irc.ERR_PASSWDMISMATCH,
57 Params: []string{"*", "Invalid username or password"},
58}}
59
60// ' ' and ':' break the IRC message wire format, '@' and '!' break prefixes,
61// '*' and '?' break masks
62const illegalNickChars = " :@!*?"
63
64// permanentDownstreamCaps is the list of always-supported downstream
65// capabilities.
66var permanentDownstreamCaps = map[string]string{
67 "batch": "",
68 "cap-notify": "",
69 "echo-message": "",
70 "message-tags": "",
71 "sasl": "PLAIN",
72 "server-time": "",
73}
74
75// needAllDownstreamCaps is the list of downstream capabilities that
76// require support from all upstreams to be enabled
77var needAllDownstreamCaps = map[string]string{
78 "away-notify": "",
79 "extended-join": "",
80 "multi-prefix": "",
81}
82
83type downstreamConn struct {
84 conn
85
86 id uint64
87
88 registered bool
89 user *user
90 nick string
91 rawUsername string
92 networkName string
93 clientName string
94 realname string
95 hostname string
96 password string // empty after authentication
97 network *network // can be nil
98
99 negociatingCaps bool
100 capVersion int
101 supportedCaps map[string]string
102 caps map[string]bool
103
104 saslServer sasl.Server
105}
106
107func newDownstreamConn(srv *Server, ic ircConn, id uint64) *downstreamConn {
108 remoteAddr := ic.RemoteAddr().String()
109 logger := &prefixLogger{srv.Logger, fmt.Sprintf("downstream %q: ", remoteAddr)}
110 options := connOptions{Logger: logger}
111 dc := &downstreamConn{
112 conn: *newConn(srv, ic, &options),
113 id: id,
114 supportedCaps: make(map[string]string),
115 caps: make(map[string]bool),
116 }
117 dc.hostname = remoteAddr
118 if host, _, err := net.SplitHostPort(dc.hostname); err == nil {
119 dc.hostname = host
120 }
121 for k, v := range permanentDownstreamCaps {
122 dc.supportedCaps[k] = v
123 }
124 if srv.LogPath != "" {
125 dc.supportedCaps["draft/chathistory"] = ""
126 }
127 return dc
128}
129
130func (dc *downstreamConn) prefix() *irc.Prefix {
131 return &irc.Prefix{
132 Name: dc.nick,
133 User: dc.user.Username,
134 Host: dc.hostname,
135 }
136}
137
138func (dc *downstreamConn) forEachNetwork(f func(*network)) {
139 if dc.network != nil {
140 f(dc.network)
141 } else {
142 dc.user.forEachNetwork(f)
143 }
144}
145
146func (dc *downstreamConn) forEachUpstream(f func(*upstreamConn)) {
147 dc.user.forEachUpstream(func(uc *upstreamConn) {
148 if dc.network != nil && uc.network != dc.network {
149 return
150 }
151 f(uc)
152 })
153}
154
155// upstream returns the upstream connection, if any. If there are zero or if
156// there are multiple upstream connections, it returns nil.
157func (dc *downstreamConn) upstream() *upstreamConn {
158 if dc.network == nil {
159 return nil
160 }
161 return dc.network.conn
162}
163
164func isOurNick(net *network, nick string) bool {
165 // TODO: this doesn't account for nick changes
166 if net.conn != nil {
167 return nick == net.conn.nick
168 }
169 // We're not currently connected to the upstream connection, so we don't
170 // know whether this name is our nickname. Best-effort: use the network's
171 // configured nickname and hope it was the one being used when we were
172 // connected.
173 return nick == net.Nick
174}
175
176// marshalEntity converts an upstream entity name (ie. channel or nick) into a
177// downstream entity name.
178//
179// This involves adding a "/<network>" suffix if the entity isn't the current
180// user.
181func (dc *downstreamConn) marshalEntity(net *network, name string) string {
182 if isOurNick(net, name) {
183 return dc.nick
184 }
185 if dc.network != nil {
186 if dc.network != net {
187 panic("soju: tried to marshal an entity for another network")
188 }
189 return name
190 }
191 return name + "/" + net.GetName()
192}
193
194func (dc *downstreamConn) marshalUserPrefix(net *network, prefix *irc.Prefix) *irc.Prefix {
195 if isOurNick(net, prefix.Name) {
196 return dc.prefix()
197 }
198 if dc.network != nil {
199 if dc.network != net {
200 panic("soju: tried to marshal a user prefix for another network")
201 }
202 return prefix
203 }
204 return &irc.Prefix{
205 Name: prefix.Name + "/" + net.GetName(),
206 User: prefix.User,
207 Host: prefix.Host,
208 }
209}
210
211// unmarshalEntity converts a downstream entity name (ie. channel or nick) into
212// an upstream entity name.
213//
214// This involves removing the "/<network>" suffix.
215func (dc *downstreamConn) unmarshalEntity(name string) (*upstreamConn, string, error) {
216 if uc := dc.upstream(); uc != nil {
217 return uc, name, nil
218 }
219
220 var conn *upstreamConn
221 if i := strings.LastIndexByte(name, '/'); i >= 0 {
222 network := name[i+1:]
223 name = name[:i]
224
225 dc.forEachUpstream(func(uc *upstreamConn) {
226 if network != uc.network.GetName() {
227 return
228 }
229 conn = uc
230 })
231 }
232
233 if conn == nil {
234 return nil, "", ircError{&irc.Message{
235 Command: irc.ERR_NOSUCHCHANNEL,
236 Params: []string{name, "No such channel"},
237 }}
238 }
239 return conn, name, nil
240}
241
242func (dc *downstreamConn) unmarshalText(uc *upstreamConn, text string) string {
243 if dc.upstream() != nil {
244 return text
245 }
246 // TODO: smarter parsing that ignores URLs
247 return strings.ReplaceAll(text, "/"+uc.network.GetName(), "")
248}
249
250func (dc *downstreamConn) readMessages(ch chan<- event) error {
251 for {
252 msg, err := dc.ReadMessage()
253 if err == io.EOF {
254 break
255 } else if err != nil {
256 return fmt.Errorf("failed to read IRC command: %v", err)
257 }
258
259 ch <- eventDownstreamMessage{msg, dc}
260 }
261
262 return nil
263}
264
265// SendMessage sends an outgoing message.
266//
267// This can only called from the user goroutine.
268func (dc *downstreamConn) SendMessage(msg *irc.Message) {
269 if !dc.caps["message-tags"] {
270 if msg.Command == "TAGMSG" {
271 return
272 }
273 msg = msg.Copy()
274 for name := range msg.Tags {
275 supported := false
276 switch name {
277 case "time":
278 supported = dc.caps["server-time"]
279 }
280 if !supported {
281 delete(msg.Tags, name)
282 }
283 }
284 }
285 if msg.Command == "JOIN" && !dc.caps["extended-join"] {
286 msg.Params = msg.Params[:1]
287 }
288
289 dc.conn.SendMessage(msg)
290}
291
292// marshalMessage re-formats a message coming from an upstream connection so
293// that it's suitable for being sent on this downstream connection. Only
294// messages that may appear in logs are supported, except MODE.
295func (dc *downstreamConn) marshalMessage(msg *irc.Message, net *network) *irc.Message {
296 msg = msg.Copy()
297 msg.Prefix = dc.marshalUserPrefix(net, msg.Prefix)
298
299 switch msg.Command {
300 case "PRIVMSG", "NOTICE", "TAGMSG":
301 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
302 case "NICK":
303 // Nick change for another user
304 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
305 case "JOIN", "PART":
306 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
307 case "KICK":
308 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
309 msg.Params[1] = dc.marshalEntity(net, msg.Params[1])
310 case "TOPIC":
311 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
312 case "QUIT":
313 // This space is intentionally left blank
314 default:
315 panic(fmt.Sprintf("unexpected %q message", msg.Command))
316 }
317
318 return msg
319}
320
321func (dc *downstreamConn) handleMessage(msg *irc.Message) error {
322 switch msg.Command {
323 case "QUIT":
324 return dc.Close()
325 default:
326 if dc.registered {
327 return dc.handleMessageRegistered(msg)
328 } else {
329 return dc.handleMessageUnregistered(msg)
330 }
331 }
332}
333
334func (dc *downstreamConn) handleMessageUnregistered(msg *irc.Message) error {
335 switch msg.Command {
336 case "NICK":
337 var nick string
338 if err := parseMessageParams(msg, &nick); err != nil {
339 return err
340 }
341 if strings.ContainsAny(nick, illegalNickChars) {
342 return ircError{&irc.Message{
343 Command: irc.ERR_ERRONEUSNICKNAME,
344 Params: []string{dc.nick, nick, "contains illegal characters"},
345 }}
346 }
347 if nick == serviceNick {
348 return ircError{&irc.Message{
349 Command: irc.ERR_NICKNAMEINUSE,
350 Params: []string{dc.nick, nick, "Nickname reserved for bouncer service"},
351 }}
352 }
353 dc.nick = nick
354 case "USER":
355 if err := parseMessageParams(msg, &dc.rawUsername, nil, nil, &dc.realname); err != nil {
356 return err
357 }
358 case "PASS":
359 if err := parseMessageParams(msg, &dc.password); err != nil {
360 return err
361 }
362 case "CAP":
363 var subCmd string
364 if err := parseMessageParams(msg, &subCmd); err != nil {
365 return err
366 }
367 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
368 return err
369 }
370 case "AUTHENTICATE":
371 if !dc.caps["sasl"] {
372 return ircError{&irc.Message{
373 Command: irc.ERR_SASLFAIL,
374 Params: []string{"*", "AUTHENTICATE requires the \"sasl\" capability to be enabled"},
375 }}
376 }
377 if len(msg.Params) == 0 {
378 return ircError{&irc.Message{
379 Command: irc.ERR_SASLFAIL,
380 Params: []string{"*", "Missing AUTHENTICATE argument"},
381 }}
382 }
383 if dc.nick == "" {
384 return ircError{&irc.Message{
385 Command: irc.ERR_SASLFAIL,
386 Params: []string{"*", "Expected NICK command before AUTHENTICATE"},
387 }}
388 }
389
390 var resp []byte
391 if dc.saslServer == nil {
392 mech := strings.ToUpper(msg.Params[0])
393 switch mech {
394 case "PLAIN":
395 dc.saslServer = sasl.NewPlainServer(sasl.PlainAuthenticator(func(identity, username, password string) error {
396 return dc.authenticate(username, password)
397 }))
398 default:
399 return ircError{&irc.Message{
400 Command: irc.ERR_SASLFAIL,
401 Params: []string{"*", fmt.Sprintf("Unsupported SASL mechanism %q", mech)},
402 }}
403 }
404 } else if msg.Params[0] == "*" {
405 dc.saslServer = nil
406 return ircError{&irc.Message{
407 Command: irc.ERR_SASLABORTED,
408 Params: []string{"*", "SASL authentication aborted"},
409 }}
410 } else if msg.Params[0] == "+" {
411 resp = nil
412 } else {
413 // TODO: multi-line messages
414 var err error
415 resp, err = base64.StdEncoding.DecodeString(msg.Params[0])
416 if err != nil {
417 dc.saslServer = nil
418 return ircError{&irc.Message{
419 Command: irc.ERR_SASLFAIL,
420 Params: []string{"*", "Invalid base64-encoded response"},
421 }}
422 }
423 }
424
425 challenge, done, err := dc.saslServer.Next(resp)
426 if err != nil {
427 dc.saslServer = nil
428 if ircErr, ok := err.(ircError); ok && ircErr.Message.Command == irc.ERR_PASSWDMISMATCH {
429 return ircError{&irc.Message{
430 Command: irc.ERR_SASLFAIL,
431 Params: []string{"*", ircErr.Message.Params[1]},
432 }}
433 }
434 dc.SendMessage(&irc.Message{
435 Prefix: dc.srv.prefix(),
436 Command: irc.ERR_SASLFAIL,
437 Params: []string{"*", "SASL error"},
438 })
439 return fmt.Errorf("SASL authentication failed: %v", err)
440 } else if done {
441 dc.saslServer = nil
442 dc.SendMessage(&irc.Message{
443 Prefix: dc.srv.prefix(),
444 Command: irc.RPL_LOGGEDIN,
445 Params: []string{dc.nick, dc.prefix().String(), dc.user.Username, "You are now logged in"},
446 })
447 dc.SendMessage(&irc.Message{
448 Prefix: dc.srv.prefix(),
449 Command: irc.RPL_SASLSUCCESS,
450 Params: []string{dc.nick, "SASL authentication successful"},
451 })
452 } else {
453 challengeStr := "+"
454 if len(challenge) > 0 {
455 challengeStr = base64.StdEncoding.EncodeToString(challenge)
456 }
457
458 // TODO: multi-line messages
459 dc.SendMessage(&irc.Message{
460 Prefix: dc.srv.prefix(),
461 Command: "AUTHENTICATE",
462 Params: []string{challengeStr},
463 })
464 }
465 default:
466 dc.logger.Printf("unhandled message: %v", msg)
467 return newUnknownCommandError(msg.Command)
468 }
469 if dc.rawUsername != "" && dc.nick != "" && !dc.negociatingCaps {
470 return dc.register()
471 }
472 return nil
473}
474
475func (dc *downstreamConn) handleCapCommand(cmd string, args []string) error {
476 cmd = strings.ToUpper(cmd)
477
478 replyTo := dc.nick
479 if !dc.registered {
480 replyTo = "*"
481 }
482
483 switch cmd {
484 case "LS":
485 if len(args) > 0 {
486 var err error
487 if dc.capVersion, err = strconv.Atoi(args[0]); err != nil {
488 return err
489 }
490 }
491
492 caps := make([]string, 0, len(dc.supportedCaps))
493 for k, v := range dc.supportedCaps {
494 if dc.capVersion >= 302 && v != "" {
495 caps = append(caps, k+"="+v)
496 } else {
497 caps = append(caps, k)
498 }
499 }
500
501 // TODO: multi-line replies
502 dc.SendMessage(&irc.Message{
503 Prefix: dc.srv.prefix(),
504 Command: "CAP",
505 Params: []string{replyTo, "LS", strings.Join(caps, " ")},
506 })
507
508 if dc.capVersion >= 302 {
509 // CAP version 302 implicitly enables cap-notify
510 dc.caps["cap-notify"] = true
511 }
512
513 if !dc.registered {
514 dc.negociatingCaps = true
515 }
516 case "LIST":
517 var caps []string
518 for name := range dc.caps {
519 caps = append(caps, name)
520 }
521
522 // TODO: multi-line replies
523 dc.SendMessage(&irc.Message{
524 Prefix: dc.srv.prefix(),
525 Command: "CAP",
526 Params: []string{replyTo, "LIST", strings.Join(caps, " ")},
527 })
528 case "REQ":
529 if len(args) == 0 {
530 return ircError{&irc.Message{
531 Command: err_invalidcapcmd,
532 Params: []string{replyTo, cmd, "Missing argument in CAP REQ command"},
533 }}
534 }
535
536 // TODO: atomically ack/nak the whole capability set
537 caps := strings.Fields(args[0])
538 ack := true
539 for _, name := range caps {
540 name = strings.ToLower(name)
541 enable := !strings.HasPrefix(name, "-")
542 if !enable {
543 name = strings.TrimPrefix(name, "-")
544 }
545
546 if enable == dc.caps[name] {
547 continue
548 }
549
550 _, ok := dc.supportedCaps[name]
551 if !ok {
552 ack = false
553 break
554 }
555
556 if name == "cap-notify" && dc.capVersion >= 302 && !enable {
557 // cap-notify cannot be disabled with CAP version 302
558 ack = false
559 break
560 }
561
562 dc.caps[name] = enable
563 }
564
565 reply := "NAK"
566 if ack {
567 reply = "ACK"
568 }
569 dc.SendMessage(&irc.Message{
570 Prefix: dc.srv.prefix(),
571 Command: "CAP",
572 Params: []string{replyTo, reply, args[0]},
573 })
574 case "END":
575 dc.negociatingCaps = false
576 default:
577 return ircError{&irc.Message{
578 Command: err_invalidcapcmd,
579 Params: []string{replyTo, cmd, "Unknown CAP command"},
580 }}
581 }
582 return nil
583}
584
585func (dc *downstreamConn) setSupportedCap(name, value string) {
586 prevValue, hasPrev := dc.supportedCaps[name]
587 changed := !hasPrev || prevValue != value
588 dc.supportedCaps[name] = value
589
590 if !dc.caps["cap-notify"] || !changed {
591 return
592 }
593
594 replyTo := dc.nick
595 if !dc.registered {
596 replyTo = "*"
597 }
598
599 cap := name
600 if value != "" && dc.capVersion >= 302 {
601 cap = name + "=" + value
602 }
603
604 dc.SendMessage(&irc.Message{
605 Prefix: dc.srv.prefix(),
606 Command: "CAP",
607 Params: []string{replyTo, "NEW", cap},
608 })
609}
610
611func (dc *downstreamConn) unsetSupportedCap(name string) {
612 _, hasPrev := dc.supportedCaps[name]
613 delete(dc.supportedCaps, name)
614 delete(dc.caps, name)
615
616 if !dc.caps["cap-notify"] || !hasPrev {
617 return
618 }
619
620 replyTo := dc.nick
621 if !dc.registered {
622 replyTo = "*"
623 }
624
625 dc.SendMessage(&irc.Message{
626 Prefix: dc.srv.prefix(),
627 Command: "CAP",
628 Params: []string{replyTo, "DEL", name},
629 })
630}
631
632func (dc *downstreamConn) updateSupportedCaps() {
633 supportedCaps := make(map[string]bool)
634 for cap := range needAllDownstreamCaps {
635 supportedCaps[cap] = true
636 }
637 dc.forEachUpstream(func(uc *upstreamConn) {
638 for cap, supported := range supportedCaps {
639 supportedCaps[cap] = supported && uc.caps[cap]
640 }
641 })
642
643 for cap, supported := range supportedCaps {
644 if supported {
645 dc.setSupportedCap(cap, needAllDownstreamCaps[cap])
646 } else {
647 dc.unsetSupportedCap(cap)
648 }
649 }
650}
651
652func (dc *downstreamConn) updateNick() {
653 if uc := dc.upstream(); uc != nil && uc.nick != dc.nick {
654 dc.SendMessage(&irc.Message{
655 Prefix: dc.prefix(),
656 Command: "NICK",
657 Params: []string{uc.nick},
658 })
659 dc.nick = uc.nick
660 }
661}
662
663func sanityCheckServer(addr string) error {
664 dialer := net.Dialer{Timeout: 30 * time.Second}
665 conn, err := tls.DialWithDialer(&dialer, "tcp", addr, nil)
666 if err != nil {
667 return err
668 }
669 return conn.Close()
670}
671
672func unmarshalUsername(rawUsername string) (username, client, network string) {
673 username = rawUsername
674
675 i := strings.IndexAny(username, "/@")
676 j := strings.LastIndexAny(username, "/@")
677 if i >= 0 {
678 username = rawUsername[:i]
679 }
680 if j >= 0 {
681 if rawUsername[j] == '@' {
682 client = rawUsername[j+1:]
683 } else {
684 network = rawUsername[j+1:]
685 }
686 }
687 if i >= 0 && j >= 0 && i < j {
688 if rawUsername[i] == '@' {
689 client = rawUsername[i+1 : j]
690 } else {
691 network = rawUsername[i+1 : j]
692 }
693 }
694
695 return username, client, network
696}
697
698func (dc *downstreamConn) authenticate(username, password string) error {
699 username, clientName, networkName := unmarshalUsername(username)
700
701 u, err := dc.srv.db.GetUser(username)
702 if err != nil {
703 dc.logger.Printf("failed authentication for %q: %v", username, err)
704 return errAuthFailed
705 }
706
707 // Password auth disabled
708 if u.Password == "" {
709 return errAuthFailed
710 }
711
712 err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
713 if err != nil {
714 dc.logger.Printf("failed authentication for %q: %v", username, err)
715 return errAuthFailed
716 }
717
718 dc.user = dc.srv.getUser(username)
719 if dc.user == nil {
720 dc.logger.Printf("failed authentication for %q: user not active", username)
721 return errAuthFailed
722 }
723 dc.clientName = clientName
724 dc.networkName = networkName
725 return nil
726}
727
728func (dc *downstreamConn) register() error {
729 if dc.registered {
730 return fmt.Errorf("tried to register twice")
731 }
732
733 password := dc.password
734 dc.password = ""
735 if dc.user == nil {
736 if err := dc.authenticate(dc.rawUsername, password); err != nil {
737 return err
738 }
739 }
740
741 if dc.clientName == "" && dc.networkName == "" {
742 _, dc.clientName, dc.networkName = unmarshalUsername(dc.rawUsername)
743 }
744
745 dc.registered = true
746 dc.logger.Printf("registration complete for user %q", dc.user.Username)
747 return nil
748}
749
750func (dc *downstreamConn) loadNetwork() error {
751 if dc.networkName == "" {
752 return nil
753 }
754
755 network := dc.user.getNetwork(dc.networkName)
756 if network == nil {
757 addr := dc.networkName
758 if !strings.ContainsRune(addr, ':') {
759 addr = addr + ":6697"
760 }
761
762 dc.logger.Printf("trying to connect to new network %q", addr)
763 if err := sanityCheckServer(addr); err != nil {
764 dc.logger.Printf("failed to connect to %q: %v", addr, err)
765 return ircError{&irc.Message{
766 Command: irc.ERR_PASSWDMISMATCH,
767 Params: []string{"*", fmt.Sprintf("Failed to connect to %q", dc.networkName)},
768 }}
769 }
770
771 // Some clients only allow specifying the nickname (and use the
772 // nickname as a username too). Strip the network name from the
773 // nickname when auto-saving networks.
774 nick, _, _ := unmarshalUsername(dc.nick)
775
776 dc.logger.Printf("auto-saving network %q", dc.networkName)
777 var err error
778 network, err = dc.user.createNetwork(&Network{
779 Addr: dc.networkName,
780 Nick: nick,
781 })
782 if err != nil {
783 return err
784 }
785 }
786
787 dc.network = network
788 return nil
789}
790
791func (dc *downstreamConn) welcome() error {
792 if dc.user == nil || !dc.registered {
793 panic("tried to welcome an unregistered connection")
794 }
795
796 // TODO: doing this might take some time. We should do it in dc.register
797 // instead, but we'll potentially be adding a new network and this must be
798 // done in the user goroutine.
799 if err := dc.loadNetwork(); err != nil {
800 return err
801 }
802
803 dc.SendMessage(&irc.Message{
804 Prefix: dc.srv.prefix(),
805 Command: irc.RPL_WELCOME,
806 Params: []string{dc.nick, "Welcome to soju, " + dc.nick},
807 })
808 dc.SendMessage(&irc.Message{
809 Prefix: dc.srv.prefix(),
810 Command: irc.RPL_YOURHOST,
811 Params: []string{dc.nick, "Your host is " + dc.srv.Hostname},
812 })
813 dc.SendMessage(&irc.Message{
814 Prefix: dc.srv.prefix(),
815 Command: irc.RPL_CREATED,
816 Params: []string{dc.nick, "Who cares when the server was created?"},
817 })
818 dc.SendMessage(&irc.Message{
819 Prefix: dc.srv.prefix(),
820 Command: irc.RPL_MYINFO,
821 Params: []string{dc.nick, dc.srv.Hostname, "soju", "aiwroO", "OovaimnqpsrtklbeI"},
822 })
823 // TODO: RPL_ISUPPORT
824 // TODO: send CHATHISTORY in RPL_ISUPPORT when implemented
825 dc.SendMessage(&irc.Message{
826 Prefix: dc.srv.prefix(),
827 Command: irc.ERR_NOMOTD,
828 Params: []string{dc.nick, "No MOTD"},
829 })
830
831 dc.updateNick()
832
833 dc.forEachUpstream(func(uc *upstreamConn) {
834 for _, ch := range uc.channels {
835 if !ch.complete {
836 continue
837 }
838 if record, ok := uc.network.channels[ch.Name]; ok && record.Detached {
839 continue
840 }
841
842 dc.SendMessage(&irc.Message{
843 Prefix: dc.prefix(),
844 Command: "JOIN",
845 Params: []string{dc.marshalEntity(ch.conn.network, ch.Name)},
846 })
847
848 forwardChannel(dc, ch)
849 }
850 })
851
852 dc.forEachNetwork(func(net *network) {
853 // Only send history if we're the first connected client with that name
854 // for the network
855 if _, ok := net.offlineClients[dc.clientName]; ok {
856 dc.sendNetworkHistory(net)
857 delete(net.offlineClients, dc.clientName)
858 }
859
860 // Fast-forward history to last message
861 for target, history := range net.history {
862 if ch, ok := net.channels[target]; ok && ch.Detached {
863 continue
864 }
865
866 lastID, err := lastMsgID(net, target, time.Now())
867 if err != nil {
868 dc.logger.Printf("failed to get last message ID: %v", err)
869 continue
870 }
871 history.clients[dc.clientName] = lastID
872 }
873 })
874
875 return nil
876}
877
878func (dc *downstreamConn) sendNetworkHistory(net *network) {
879 if dc.caps["draft/chathistory"] || dc.srv.LogPath == "" {
880 return
881 }
882 for target, history := range net.history {
883 if ch, ok := net.channels[target]; ok && ch.Detached {
884 continue
885 }
886
887 lastDelivered, ok := history.clients[dc.clientName]
888 if !ok {
889 continue
890 }
891
892 limit := 4000
893 history, err := loadHistoryLatestID(net, target, lastDelivered, limit)
894 if err != nil {
895 dc.logger.Printf("failed to send implicit history for %q: %v", target, err)
896 continue
897 }
898
899 batchRef := "history"
900 if dc.caps["batch"] {
901 dc.SendMessage(&irc.Message{
902 Prefix: dc.srv.prefix(),
903 Command: "BATCH",
904 Params: []string{"+" + batchRef, "chathistory", dc.marshalEntity(net, target)},
905 })
906 }
907
908 for _, msg := range history {
909 // Don't replay all messages, because that would mess up client
910 // state. For instance we just sent the list of users, sending
911 // PART messages for one of these users would be incorrect.
912 ignore := true
913 switch msg.Command {
914 case "PRIVMSG", "NOTICE":
915 ignore = false
916 }
917 if ignore {
918 continue
919 }
920
921 if dc.caps["batch"] {
922 msg.Tags["batch"] = irc.TagValue(batchRef)
923 }
924 dc.SendMessage(dc.marshalMessage(msg, net))
925 }
926
927 if dc.caps["batch"] {
928 dc.SendMessage(&irc.Message{
929 Prefix: dc.srv.prefix(),
930 Command: "BATCH",
931 Params: []string{"-" + batchRef},
932 })
933 }
934 }
935}
936
937func (dc *downstreamConn) runUntilRegistered() error {
938 for !dc.registered {
939 msg, err := dc.ReadMessage()
940 if err != nil {
941 return fmt.Errorf("failed to read IRC command: %v", err)
942 }
943
944 err = dc.handleMessage(msg)
945 if ircErr, ok := err.(ircError); ok {
946 ircErr.Message.Prefix = dc.srv.prefix()
947 dc.SendMessage(ircErr.Message)
948 } else if err != nil {
949 return fmt.Errorf("failed to handle IRC command %q: %v", msg, err)
950 }
951 }
952
953 return nil
954}
955
956func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
957 switch msg.Command {
958 case "CAP":
959 var subCmd string
960 if err := parseMessageParams(msg, &subCmd); err != nil {
961 return err
962 }
963 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
964 return err
965 }
966 case "PING":
967 var source, destination string
968 if err := parseMessageParams(msg, &source); err != nil {
969 return err
970 }
971 if len(msg.Params) > 1 {
972 destination = msg.Params[1]
973 }
974 if destination != "" && destination != dc.srv.Hostname {
975 return ircError{&irc.Message{
976 Command: irc.ERR_NOSUCHSERVER,
977 Params: []string{dc.nick, destination, "No such server"},
978 }}
979 }
980 dc.SendMessage(&irc.Message{
981 Prefix: dc.srv.prefix(),
982 Command: "PONG",
983 Params: []string{dc.srv.Hostname, source},
984 })
985 return nil
986 case "USER":
987 return ircError{&irc.Message{
988 Command: irc.ERR_ALREADYREGISTERED,
989 Params: []string{dc.nick, "You may not reregister"},
990 }}
991 case "NICK":
992 var nick string
993 if err := parseMessageParams(msg, &nick); err != nil {
994 return err
995 }
996
997 var upstream *upstreamConn
998 if dc.upstream() == nil {
999 uc, unmarshaledNick, err := dc.unmarshalEntity(nick)
1000 if err == nil { // NICK nick/network: NICK only on a specific upstream
1001 upstream = uc
1002 nick = unmarshaledNick
1003 }
1004 }
1005
1006 if strings.ContainsAny(nick, illegalNickChars) {
1007 return ircError{&irc.Message{
1008 Command: irc.ERR_ERRONEUSNICKNAME,
1009 Params: []string{dc.nick, nick, "contains illegal characters"},
1010 }}
1011 }
1012
1013 var err error
1014 dc.forEachNetwork(func(n *network) {
1015 if err != nil || (upstream != nil && upstream.network != n) {
1016 return
1017 }
1018 n.Nick = nick
1019 err = dc.srv.db.StoreNetwork(dc.user.Username, &n.Network)
1020 })
1021 if err != nil {
1022 return err
1023 }
1024
1025 dc.forEachUpstream(func(uc *upstreamConn) {
1026 if upstream != nil && upstream != uc {
1027 return
1028 }
1029 uc.SendMessageLabeled(dc.id, &irc.Message{
1030 Command: "NICK",
1031 Params: []string{nick},
1032 })
1033 })
1034
1035 if dc.upstream() == nil && dc.nick != nick {
1036 dc.SendMessage(&irc.Message{
1037 Prefix: dc.prefix(),
1038 Command: "NICK",
1039 Params: []string{nick},
1040 })
1041 dc.nick = nick
1042 }
1043 case "JOIN":
1044 var namesStr string
1045 if err := parseMessageParams(msg, &namesStr); err != nil {
1046 return err
1047 }
1048
1049 var keys []string
1050 if len(msg.Params) > 1 {
1051 keys = strings.Split(msg.Params[1], ",")
1052 }
1053
1054 for i, name := range strings.Split(namesStr, ",") {
1055 uc, upstreamName, err := dc.unmarshalEntity(name)
1056 if err != nil {
1057 return err
1058 }
1059
1060 var key string
1061 if len(keys) > i {
1062 key = keys[i]
1063 }
1064
1065 params := []string{upstreamName}
1066 if key != "" {
1067 params = append(params, key)
1068 }
1069 uc.SendMessageLabeled(dc.id, &irc.Message{
1070 Command: "JOIN",
1071 Params: params,
1072 })
1073
1074 ch := &Channel{Name: upstreamName, Key: key, Detached: false}
1075 if current, ok := uc.network.channels[ch.Name]; ok && key == "" {
1076 // Don't clear the channel key if there's one set
1077 // TODO: add a way to unset the channel key
1078 ch.Key = current.Key
1079 }
1080 if err := uc.network.createUpdateChannel(ch); err != nil {
1081 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1082 }
1083 }
1084 case "PART":
1085 var namesStr string
1086 if err := parseMessageParams(msg, &namesStr); err != nil {
1087 return err
1088 }
1089
1090 var reason string
1091 if len(msg.Params) > 1 {
1092 reason = msg.Params[1]
1093 }
1094
1095 for _, name := range strings.Split(namesStr, ",") {
1096 uc, upstreamName, err := dc.unmarshalEntity(name)
1097 if err != nil {
1098 return err
1099 }
1100
1101 if strings.EqualFold(reason, "detach") {
1102 ch := &Channel{Name: upstreamName, Detached: true}
1103 if err := uc.network.createUpdateChannel(ch); err != nil {
1104 dc.logger.Printf("failed to detach channel %q: %v", upstreamName, err)
1105 }
1106 } else {
1107 params := []string{upstreamName}
1108 if reason != "" {
1109 params = append(params, reason)
1110 }
1111 uc.SendMessageLabeled(dc.id, &irc.Message{
1112 Command: "PART",
1113 Params: params,
1114 })
1115
1116 if err := uc.network.deleteChannel(upstreamName); err != nil {
1117 dc.logger.Printf("failed to delete channel %q: %v", upstreamName, err)
1118 }
1119 }
1120 }
1121 case "KICK":
1122 var channelStr, userStr string
1123 if err := parseMessageParams(msg, &channelStr, &userStr); err != nil {
1124 return err
1125 }
1126
1127 channels := strings.Split(channelStr, ",")
1128 users := strings.Split(userStr, ",")
1129
1130 var reason string
1131 if len(msg.Params) > 2 {
1132 reason = msg.Params[2]
1133 }
1134
1135 if len(channels) != 1 && len(channels) != len(users) {
1136 return ircError{&irc.Message{
1137 Command: irc.ERR_BADCHANMASK,
1138 Params: []string{dc.nick, channelStr, "Bad channel mask"},
1139 }}
1140 }
1141
1142 for i, user := range users {
1143 var channel string
1144 if len(channels) == 1 {
1145 channel = channels[0]
1146 } else {
1147 channel = channels[i]
1148 }
1149
1150 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1151 if err != nil {
1152 return err
1153 }
1154
1155 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1156 if err != nil {
1157 return err
1158 }
1159
1160 if ucChannel != ucUser {
1161 return ircError{&irc.Message{
1162 Command: irc.ERR_USERNOTINCHANNEL,
1163 Params: []string{dc.nick, user, channel, "They are on another network"},
1164 }}
1165 }
1166 uc := ucChannel
1167
1168 params := []string{upstreamChannel, upstreamUser}
1169 if reason != "" {
1170 params = append(params, reason)
1171 }
1172 uc.SendMessageLabeled(dc.id, &irc.Message{
1173 Command: "KICK",
1174 Params: params,
1175 })
1176 }
1177 case "MODE":
1178 var name string
1179 if err := parseMessageParams(msg, &name); err != nil {
1180 return err
1181 }
1182
1183 var modeStr string
1184 if len(msg.Params) > 1 {
1185 modeStr = msg.Params[1]
1186 }
1187
1188 if name == dc.nick {
1189 if modeStr != "" {
1190 dc.forEachUpstream(func(uc *upstreamConn) {
1191 uc.SendMessageLabeled(dc.id, &irc.Message{
1192 Command: "MODE",
1193 Params: []string{uc.nick, modeStr},
1194 })
1195 })
1196 } else {
1197 dc.SendMessage(&irc.Message{
1198 Prefix: dc.srv.prefix(),
1199 Command: irc.RPL_UMODEIS,
1200 Params: []string{dc.nick, ""}, // TODO
1201 })
1202 }
1203 return nil
1204 }
1205
1206 uc, upstreamName, err := dc.unmarshalEntity(name)
1207 if err != nil {
1208 return err
1209 }
1210
1211 if !uc.isChannel(upstreamName) {
1212 return ircError{&irc.Message{
1213 Command: irc.ERR_USERSDONTMATCH,
1214 Params: []string{dc.nick, "Cannot change mode for other users"},
1215 }}
1216 }
1217
1218 if modeStr != "" {
1219 params := []string{upstreamName, modeStr}
1220 params = append(params, msg.Params[2:]...)
1221 uc.SendMessageLabeled(dc.id, &irc.Message{
1222 Command: "MODE",
1223 Params: params,
1224 })
1225 } else {
1226 ch, ok := uc.channels[upstreamName]
1227 if !ok {
1228 return ircError{&irc.Message{
1229 Command: irc.ERR_NOSUCHCHANNEL,
1230 Params: []string{dc.nick, name, "No such channel"},
1231 }}
1232 }
1233
1234 if ch.modes == nil {
1235 // we haven't received the initial RPL_CHANNELMODEIS yet
1236 // ignore the request, we will broadcast the modes later when we receive RPL_CHANNELMODEIS
1237 return nil
1238 }
1239
1240 modeStr, modeParams := ch.modes.Format()
1241 params := []string{dc.nick, name, modeStr}
1242 params = append(params, modeParams...)
1243
1244 dc.SendMessage(&irc.Message{
1245 Prefix: dc.srv.prefix(),
1246 Command: irc.RPL_CHANNELMODEIS,
1247 Params: params,
1248 })
1249 if ch.creationTime != "" {
1250 dc.SendMessage(&irc.Message{
1251 Prefix: dc.srv.prefix(),
1252 Command: rpl_creationtime,
1253 Params: []string{dc.nick, name, ch.creationTime},
1254 })
1255 }
1256 }
1257 case "TOPIC":
1258 var channel string
1259 if err := parseMessageParams(msg, &channel); err != nil {
1260 return err
1261 }
1262
1263 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1264 if err != nil {
1265 return err
1266 }
1267
1268 if len(msg.Params) > 1 { // setting topic
1269 topic := msg.Params[1]
1270 uc.SendMessageLabeled(dc.id, &irc.Message{
1271 Command: "TOPIC",
1272 Params: []string{upstreamChannel, topic},
1273 })
1274 } else { // getting topic
1275 ch, ok := uc.channels[upstreamChannel]
1276 if !ok {
1277 return ircError{&irc.Message{
1278 Command: irc.ERR_NOSUCHCHANNEL,
1279 Params: []string{dc.nick, upstreamChannel, "No such channel"},
1280 }}
1281 }
1282 sendTopic(dc, ch)
1283 }
1284 case "LIST":
1285 // TODO: support ELIST when supported by all upstreams
1286
1287 pl := pendingLIST{
1288 downstreamID: dc.id,
1289 pendingCommands: make(map[int64]*irc.Message),
1290 }
1291 var upstream *upstreamConn
1292 var upstreamChannels map[int64][]string
1293 if len(msg.Params) > 0 {
1294 uc, upstreamMask, err := dc.unmarshalEntity(msg.Params[0])
1295 if err == nil && upstreamMask == "*" { // LIST */network: send LIST only to one network
1296 upstream = uc
1297 } else {
1298 upstreamChannels = make(map[int64][]string)
1299 channels := strings.Split(msg.Params[0], ",")
1300 for _, channel := range channels {
1301 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1302 if err != nil {
1303 return err
1304 }
1305 upstreamChannels[uc.network.ID] = append(upstreamChannels[uc.network.ID], upstreamChannel)
1306 }
1307 }
1308 }
1309
1310 dc.user.pendingLISTs = append(dc.user.pendingLISTs, pl)
1311 dc.forEachUpstream(func(uc *upstreamConn) {
1312 if upstream != nil && upstream != uc {
1313 return
1314 }
1315 var params []string
1316 if upstreamChannels != nil {
1317 if channels, ok := upstreamChannels[uc.network.ID]; ok {
1318 params = []string{strings.Join(channels, ",")}
1319 } else {
1320 return
1321 }
1322 }
1323 pl.pendingCommands[uc.network.ID] = &irc.Message{
1324 Command: "LIST",
1325 Params: params,
1326 }
1327 uc.trySendLIST(dc.id)
1328 })
1329 case "NAMES":
1330 if len(msg.Params) == 0 {
1331 dc.SendMessage(&irc.Message{
1332 Prefix: dc.srv.prefix(),
1333 Command: irc.RPL_ENDOFNAMES,
1334 Params: []string{dc.nick, "*", "End of /NAMES list"},
1335 })
1336 return nil
1337 }
1338
1339 channels := strings.Split(msg.Params[0], ",")
1340 for _, channel := range channels {
1341 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1342 if err != nil {
1343 return err
1344 }
1345
1346 ch, ok := uc.channels[upstreamChannel]
1347 if ok {
1348 sendNames(dc, ch)
1349 } else {
1350 // NAMES on a channel we have not joined, ask upstream
1351 uc.SendMessageLabeled(dc.id, &irc.Message{
1352 Command: "NAMES",
1353 Params: []string{upstreamChannel},
1354 })
1355 }
1356 }
1357 case "WHO":
1358 if len(msg.Params) == 0 {
1359 // TODO: support WHO without parameters
1360 dc.SendMessage(&irc.Message{
1361 Prefix: dc.srv.prefix(),
1362 Command: irc.RPL_ENDOFWHO,
1363 Params: []string{dc.nick, "*", "End of /WHO list"},
1364 })
1365 return nil
1366 }
1367
1368 // TODO: support WHO masks
1369 entity := msg.Params[0]
1370
1371 if entity == dc.nick {
1372 // TODO: support AWAY (H/G) in self WHO reply
1373 dc.SendMessage(&irc.Message{
1374 Prefix: dc.srv.prefix(),
1375 Command: irc.RPL_WHOREPLY,
1376 Params: []string{dc.nick, "*", dc.user.Username, dc.hostname, dc.srv.Hostname, dc.nick, "H", "0 " + dc.realname},
1377 })
1378 dc.SendMessage(&irc.Message{
1379 Prefix: dc.srv.prefix(),
1380 Command: irc.RPL_ENDOFWHO,
1381 Params: []string{dc.nick, dc.nick, "End of /WHO list"},
1382 })
1383 return nil
1384 }
1385 if entity == serviceNick {
1386 dc.SendMessage(&irc.Message{
1387 Prefix: dc.srv.prefix(),
1388 Command: irc.RPL_WHOREPLY,
1389 Params: []string{serviceNick, "*", servicePrefix.User, servicePrefix.Host, dc.srv.Hostname, serviceNick, "H", "0 " + serviceRealname},
1390 })
1391 dc.SendMessage(&irc.Message{
1392 Prefix: dc.srv.prefix(),
1393 Command: irc.RPL_ENDOFWHO,
1394 Params: []string{dc.nick, serviceNick, "End of /WHO list"},
1395 })
1396 return nil
1397 }
1398
1399 uc, upstreamName, err := dc.unmarshalEntity(entity)
1400 if err != nil {
1401 return err
1402 }
1403
1404 var params []string
1405 if len(msg.Params) == 2 {
1406 params = []string{upstreamName, msg.Params[1]}
1407 } else {
1408 params = []string{upstreamName}
1409 }
1410
1411 uc.SendMessageLabeled(dc.id, &irc.Message{
1412 Command: "WHO",
1413 Params: params,
1414 })
1415 case "WHOIS":
1416 if len(msg.Params) == 0 {
1417 return ircError{&irc.Message{
1418 Command: irc.ERR_NONICKNAMEGIVEN,
1419 Params: []string{dc.nick, "No nickname given"},
1420 }}
1421 }
1422
1423 var target, mask string
1424 if len(msg.Params) == 1 {
1425 target = ""
1426 mask = msg.Params[0]
1427 } else {
1428 target = msg.Params[0]
1429 mask = msg.Params[1]
1430 }
1431 // TODO: support multiple WHOIS users
1432 if i := strings.IndexByte(mask, ','); i >= 0 {
1433 mask = mask[:i]
1434 }
1435
1436 if mask == dc.nick {
1437 dc.SendMessage(&irc.Message{
1438 Prefix: dc.srv.prefix(),
1439 Command: irc.RPL_WHOISUSER,
1440 Params: []string{dc.nick, dc.nick, dc.user.Username, dc.hostname, "*", dc.realname},
1441 })
1442 dc.SendMessage(&irc.Message{
1443 Prefix: dc.srv.prefix(),
1444 Command: irc.RPL_WHOISSERVER,
1445 Params: []string{dc.nick, dc.nick, dc.srv.Hostname, "soju"},
1446 })
1447 dc.SendMessage(&irc.Message{
1448 Prefix: dc.srv.prefix(),
1449 Command: irc.RPL_ENDOFWHOIS,
1450 Params: []string{dc.nick, dc.nick, "End of /WHOIS list"},
1451 })
1452 return nil
1453 }
1454
1455 // TODO: support WHOIS masks
1456 uc, upstreamNick, err := dc.unmarshalEntity(mask)
1457 if err != nil {
1458 return err
1459 }
1460
1461 var params []string
1462 if target != "" {
1463 if target == mask { // WHOIS nick nick
1464 params = []string{upstreamNick, upstreamNick}
1465 } else {
1466 params = []string{target, upstreamNick}
1467 }
1468 } else {
1469 params = []string{upstreamNick}
1470 }
1471
1472 uc.SendMessageLabeled(dc.id, &irc.Message{
1473 Command: "WHOIS",
1474 Params: params,
1475 })
1476 case "PRIVMSG":
1477 var targetsStr, text string
1478 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1479 return err
1480 }
1481 tags := copyClientTags(msg.Tags)
1482
1483 for _, name := range strings.Split(targetsStr, ",") {
1484 if name == serviceNick {
1485 handleServicePRIVMSG(dc, text)
1486 continue
1487 }
1488
1489 uc, upstreamName, err := dc.unmarshalEntity(name)
1490 if err != nil {
1491 return err
1492 }
1493
1494 if upstreamName == "NickServ" {
1495 dc.handleNickServPRIVMSG(uc, text)
1496 }
1497
1498 unmarshaledText := text
1499 if uc.isChannel(upstreamName) {
1500 unmarshaledText = dc.unmarshalText(uc, text)
1501 }
1502 uc.SendMessageLabeled(dc.id, &irc.Message{
1503 Tags: tags,
1504 Command: "PRIVMSG",
1505 Params: []string{upstreamName, unmarshaledText},
1506 })
1507
1508 echoTags := tags.Copy()
1509 echoTags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
1510 echoMsg := &irc.Message{
1511 Tags: echoTags,
1512 Prefix: &irc.Prefix{
1513 Name: uc.nick,
1514 User: uc.username,
1515 },
1516 Command: "PRIVMSG",
1517 Params: []string{upstreamName, text},
1518 }
1519 uc.produce(upstreamName, echoMsg, dc)
1520 }
1521 case "NOTICE":
1522 var targetsStr, text string
1523 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1524 return err
1525 }
1526 tags := copyClientTags(msg.Tags)
1527
1528 for _, name := range strings.Split(targetsStr, ",") {
1529 uc, upstreamName, err := dc.unmarshalEntity(name)
1530 if err != nil {
1531 return err
1532 }
1533
1534 unmarshaledText := text
1535 if uc.isChannel(upstreamName) {
1536 unmarshaledText = dc.unmarshalText(uc, text)
1537 }
1538 uc.SendMessageLabeled(dc.id, &irc.Message{
1539 Tags: tags,
1540 Command: "NOTICE",
1541 Params: []string{upstreamName, unmarshaledText},
1542 })
1543 }
1544 case "TAGMSG":
1545 var targetsStr string
1546 if err := parseMessageParams(msg, &targetsStr); err != nil {
1547 return err
1548 }
1549 tags := copyClientTags(msg.Tags)
1550
1551 for _, name := range strings.Split(targetsStr, ",") {
1552 uc, upstreamName, err := dc.unmarshalEntity(name)
1553 if err != nil {
1554 return err
1555 }
1556
1557 uc.SendMessageLabeled(dc.id, &irc.Message{
1558 Tags: tags,
1559 Command: "TAGMSG",
1560 Params: []string{upstreamName},
1561 })
1562 }
1563 case "INVITE":
1564 var user, channel string
1565 if err := parseMessageParams(msg, &user, &channel); err != nil {
1566 return err
1567 }
1568
1569 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1570 if err != nil {
1571 return err
1572 }
1573
1574 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1575 if err != nil {
1576 return err
1577 }
1578
1579 if ucChannel != ucUser {
1580 return ircError{&irc.Message{
1581 Command: irc.ERR_USERNOTINCHANNEL,
1582 Params: []string{dc.nick, user, channel, "They are on another network"},
1583 }}
1584 }
1585 uc := ucChannel
1586
1587 uc.SendMessageLabeled(dc.id, &irc.Message{
1588 Command: "INVITE",
1589 Params: []string{upstreamUser, upstreamChannel},
1590 })
1591 case "CHATHISTORY":
1592 var subcommand string
1593 if err := parseMessageParams(msg, &subcommand); err != nil {
1594 return err
1595 }
1596 var target, criteria, limitStr string
1597 if err := parseMessageParams(msg, nil, &target, &criteria, &limitStr); err != nil {
1598 return ircError{&irc.Message{
1599 Command: "FAIL",
1600 Params: []string{"CHATHISTORY", "NEED_MORE_PARAMS", subcommand, "Missing parameters"},
1601 }}
1602 }
1603
1604 if dc.srv.LogPath == "" {
1605 return ircError{&irc.Message{
1606 Command: irc.ERR_UNKNOWNCOMMAND,
1607 Params: []string{dc.nick, subcommand, "Unknown command"},
1608 }}
1609 }
1610
1611 uc, entity, err := dc.unmarshalEntity(target)
1612 if err != nil {
1613 return err
1614 }
1615
1616 // TODO: support msgid criteria
1617 criteriaParts := strings.SplitN(criteria, "=", 2)
1618 if len(criteriaParts) != 2 || criteriaParts[0] != "timestamp" {
1619 return ircError{&irc.Message{
1620 Command: "FAIL",
1621 Params: []string{"CHATHISTORY", "UNKNOWN_CRITERIA", criteria, "Unknown criteria"},
1622 }}
1623 }
1624
1625 timestamp, err := time.Parse(serverTimeLayout, criteriaParts[1])
1626 if err != nil {
1627 return ircError{&irc.Message{
1628 Command: "FAIL",
1629 Params: []string{"CHATHISTORY", "INVALID_CRITERIA", criteria, "Invalid criteria"},
1630 }}
1631 }
1632
1633 limit, err := strconv.Atoi(limitStr)
1634 if err != nil || limit < 0 || limit > dc.srv.HistoryLimit {
1635 return ircError{&irc.Message{
1636 Command: "FAIL",
1637 Params: []string{"CHATHISTORY", "INVALID_LIMIT", limitStr, "Invalid limit"},
1638 }}
1639 }
1640
1641 var history []*irc.Message
1642 switch subcommand {
1643 case "BEFORE":
1644 history, err = loadHistoryBeforeTime(uc.network, entity, timestamp, limit)
1645 case "AFTER":
1646 history, err = loadHistoryAfterTime(uc.network, entity, timestamp, limit)
1647 default:
1648 // TODO: support LATEST, BETWEEN
1649 return ircError{&irc.Message{
1650 Command: "FAIL",
1651 Params: []string{"CHATHISTORY", "UNKNOWN_COMMAND", subcommand, "Unknown command"},
1652 }}
1653 }
1654 if err != nil {
1655 dc.logger.Printf("failed parsing log messages for chathistory: %v", err)
1656 return newChatHistoryError(subcommand, target)
1657 }
1658
1659 batchRef := "history"
1660 dc.SendMessage(&irc.Message{
1661 Prefix: dc.srv.prefix(),
1662 Command: "BATCH",
1663 Params: []string{"+" + batchRef, "chathistory", target},
1664 })
1665
1666 for _, msg := range history {
1667 msg.Tags["batch"] = irc.TagValue(batchRef)
1668 dc.SendMessage(dc.marshalMessage(msg, uc.network))
1669 }
1670
1671 dc.SendMessage(&irc.Message{
1672 Prefix: dc.srv.prefix(),
1673 Command: "BATCH",
1674 Params: []string{"-" + batchRef},
1675 })
1676 default:
1677 dc.logger.Printf("unhandled message: %v", msg)
1678 return newUnknownCommandError(msg.Command)
1679 }
1680 return nil
1681}
1682
1683func (dc *downstreamConn) handleNickServPRIVMSG(uc *upstreamConn, text string) {
1684 username, password, ok := parseNickServCredentials(text, uc.nick)
1685 if !ok {
1686 return
1687 }
1688
1689 // User may have e.g. EXTERNAL mechanism configured. We do not want to
1690 // automatically erase the key pair or any other credentials.
1691 if uc.network.SASL.Mechanism != "" && uc.network.SASL.Mechanism != "PLAIN" {
1692 return
1693 }
1694
1695 dc.logger.Printf("auto-saving NickServ credentials with username %q", username)
1696 n := uc.network
1697 n.SASL.Mechanism = "PLAIN"
1698 n.SASL.Plain.Username = username
1699 n.SASL.Plain.Password = password
1700 if err := dc.srv.db.StoreNetwork(dc.user.Username, &n.Network); err != nil {
1701 dc.logger.Printf("failed to save NickServ credentials: %v", err)
1702 }
1703}
1704
1705func parseNickServCredentials(text, nick string) (username, password string, ok bool) {
1706 fields := strings.Fields(text)
1707 if len(fields) < 2 {
1708 return "", "", false
1709 }
1710 cmd := strings.ToUpper(fields[0])
1711 params := fields[1:]
1712 switch cmd {
1713 case "REGISTER":
1714 username = nick
1715 password = params[0]
1716 case "IDENTIFY":
1717 if len(params) == 1 {
1718 username = nick
1719 password = params[0]
1720 } else {
1721 username = params[0]
1722 password = params[1]
1723 }
1724 case "SET":
1725 if len(params) == 2 && strings.EqualFold(params[0], "PASSWORD") {
1726 username = nick
1727 password = params[1]
1728 }
1729 default:
1730 return "", "", false
1731 }
1732 return username, password, true
1733}
Note: See TracBrowser for help on using the repository browser.