source: code/trunk/downstream.go@ 409

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

Nuke in-memory ring buffer

Instead, always read chat history from logs. Unify the implicit chat
history (pushing history to clients) and explicit chat history
(via the CHATHISTORY command).

Instead of keeping track of ring buffer cursors for each client, use
message IDs.

If necessary, the ring buffer could be re-introduced behind a
common MessageStore interface (could be useful when on-disk logs are
disabled).

References: https://todo.sr.ht/~emersion/soju/80

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