source: code/trunk/downstream.go@ 335

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

Add support for WebSocket connections

WebSocket connections allow web-based clients to connect to IRC. This
commit implements the WebSocket sub-protocol as specified by the pending
IRCv3 proposal [1].

WebSocket listeners can now be set up via a "wss" protocol in the
listen directive. The new http-origin directive allows the CORS
allowed origins to be configured.

[1]: https://github.com/ircv3/ircv3-specifications/pull/342

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