source: code/trunk/downstream.go@ 320

Last change on this file since 320 was 319, checked in by delthas, 5 years ago

Add support for downstream CHATHISTORY

This adds support for the WIP (at the time of this commit)
draft/chathistory extension, based on the draft at [1] and the
additional comments at [2].

This gets the history by parsing the chat logs, and is therefore only
enabled when the logs are enabled and the log path is configured.

Getting the history only from the logs adds some restrictions:

  • we cannot get history by msgid (those are not logged)
  • we cannot get the users masks (maybe they could be inferred from the JOIN etc, but it is not worth the effort and would not work every time)

The regular soju network history is not sent to clients that support
draft/chathistory, so that they can fetch what they need by manually
calling CHATHISTORY.

The only supported command is BEFORE for now, because that is the only
required command for an app that offers an "infinite history scrollback"
feature.

Regarding implementation, rather than reading the file from the end in
reverse, we simply start from the beginning of each log file, store each
PRIVMSG into a ring, then add the last lines of that ring into the
history we'll return later. The message parsing implementation must be
kept somewhat fast because an app could potentially request thousands of
messages in several files. Here we are using simple sscanf and indexOf
rather than regexps.

In case some log files do not contain any message (for example because
the user had not joined a channel at that time), we try up to a 100 days
of empty log files before giving up.

[1]: https://github.com/prawnsalad/ircv3-specifications/pull/3/files
[2]: https://github.com/ircv3/ircv3-specifications/pull/393/files#r350210018

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, netConn net.Conn, id uint64) *downstreamConn {
103 logger := &prefixLogger{srv.Logger, fmt.Sprintf("downstream %q: ", netConn.RemoteAddr())}
104 dc := &downstreamConn{
105 conn: *newConn(srv, netIRCConn(netConn), logger),
106 id: id,
107 supportedCaps: make(map[string]string),
108 caps: make(map[string]bool),
109 }
110 dc.hostname = netConn.RemoteAddr().String()
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 err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
692 if err != nil {
693 dc.logger.Printf("failed authentication for %q: %v", username, err)
694 return errAuthFailed
695 }
696
697 dc.user = dc.srv.getUser(username)
698 if dc.user == nil {
699 dc.logger.Printf("failed authentication for %q: user not active", username)
700 return errAuthFailed
701 }
702 dc.clientName = clientName
703 dc.networkName = networkName
704 return nil
705}
706
707func (dc *downstreamConn) register() error {
708 if dc.registered {
709 return fmt.Errorf("tried to register twice")
710 }
711
712 password := dc.password
713 dc.password = ""
714 if dc.user == nil {
715 if err := dc.authenticate(dc.rawUsername, password); err != nil {
716 return err
717 }
718 }
719
720 if dc.clientName == "" && dc.networkName == "" {
721 _, dc.clientName, dc.networkName = unmarshalUsername(dc.rawUsername)
722 }
723
724 dc.registered = true
725 dc.logger.Printf("registration complete for user %q", dc.user.Username)
726 return nil
727}
728
729func (dc *downstreamConn) loadNetwork() error {
730 if dc.networkName == "" {
731 return nil
732 }
733
734 network := dc.user.getNetwork(dc.networkName)
735 if network == nil {
736 addr := dc.networkName
737 if !strings.ContainsRune(addr, ':') {
738 addr = addr + ":6697"
739 }
740
741 dc.logger.Printf("trying to connect to new network %q", addr)
742 if err := sanityCheckServer(addr); err != nil {
743 dc.logger.Printf("failed to connect to %q: %v", addr, err)
744 return ircError{&irc.Message{
745 Command: irc.ERR_PASSWDMISMATCH,
746 Params: []string{"*", fmt.Sprintf("Failed to connect to %q", dc.networkName)},
747 }}
748 }
749
750 dc.logger.Printf("auto-saving network %q", dc.networkName)
751 var err error
752 network, err = dc.user.createNetwork(&Network{
753 Addr: dc.networkName,
754 Nick: dc.nick,
755 })
756 if err != nil {
757 return err
758 }
759 }
760
761 dc.network = network
762 return nil
763}
764
765func (dc *downstreamConn) welcome() error {
766 if dc.user == nil || !dc.registered {
767 panic("tried to welcome an unregistered connection")
768 }
769
770 // TODO: doing this might take some time. We should do it in dc.register
771 // instead, but we'll potentially be adding a new network and this must be
772 // done in the user goroutine.
773 if err := dc.loadNetwork(); err != nil {
774 return err
775 }
776
777 dc.SendMessage(&irc.Message{
778 Prefix: dc.srv.prefix(),
779 Command: irc.RPL_WELCOME,
780 Params: []string{dc.nick, "Welcome to soju, " + dc.nick},
781 })
782 dc.SendMessage(&irc.Message{
783 Prefix: dc.srv.prefix(),
784 Command: irc.RPL_YOURHOST,
785 Params: []string{dc.nick, "Your host is " + dc.srv.Hostname},
786 })
787 dc.SendMessage(&irc.Message{
788 Prefix: dc.srv.prefix(),
789 Command: irc.RPL_CREATED,
790 Params: []string{dc.nick, "Who cares when the server was created?"},
791 })
792 dc.SendMessage(&irc.Message{
793 Prefix: dc.srv.prefix(),
794 Command: irc.RPL_MYINFO,
795 Params: []string{dc.nick, dc.srv.Hostname, "soju", "aiwroO", "OovaimnqpsrtklbeI"},
796 })
797 // TODO: RPL_ISUPPORT
798 // TODO: send CHATHISTORY in RPL_ISUPPORT when implemented
799 dc.SendMessage(&irc.Message{
800 Prefix: dc.srv.prefix(),
801 Command: irc.ERR_NOMOTD,
802 Params: []string{dc.nick, "No MOTD"},
803 })
804
805 dc.updateNick()
806
807 dc.forEachUpstream(func(uc *upstreamConn) {
808 for _, ch := range uc.channels {
809 if !ch.complete {
810 continue
811 }
812 if record, ok := uc.network.channels[ch.Name]; ok && record.Detached {
813 continue
814 }
815
816 dc.SendMessage(&irc.Message{
817 Prefix: dc.prefix(),
818 Command: "JOIN",
819 Params: []string{dc.marshalEntity(ch.conn.network, ch.Name)},
820 })
821
822 forwardChannel(dc, ch)
823 }
824 })
825
826 dc.forEachNetwork(func(net *network) {
827 // Only send history if we're the first connected client with that name
828 // for the network
829 if _, ok := net.offlineClients[dc.clientName]; ok {
830 dc.sendNetworkHistory(net)
831 delete(net.offlineClients, dc.clientName)
832 }
833 })
834
835 return nil
836}
837
838func (dc *downstreamConn) sendNetworkHistory(net *network) {
839 if dc.caps["draft/chathistory"] {
840 return
841 }
842 for target, history := range net.history {
843 if ch, ok := net.channels[target]; ok && ch.Detached {
844 continue
845 }
846
847 seq, ok := history.offlineClients[dc.clientName]
848 if !ok {
849 continue
850 }
851 delete(history.offlineClients, dc.clientName)
852
853 // If all clients have received history, no need to keep the
854 // ring buffer around
855 if len(history.offlineClients) == 0 {
856 delete(net.history, target)
857 }
858
859 consumer := history.ring.NewConsumer(seq)
860
861 batchRef := "history"
862 if dc.caps["batch"] {
863 dc.SendMessage(&irc.Message{
864 Prefix: dc.srv.prefix(),
865 Command: "BATCH",
866 Params: []string{"+" + batchRef, "chathistory", dc.marshalEntity(net, target)},
867 })
868 }
869
870 for {
871 msg := consumer.Consume()
872 if msg == nil {
873 break
874 }
875
876 // Don't replay all messages, because that would mess up client
877 // state. For instance we just sent the list of users, sending
878 // PART messages for one of these users would be incorrect.
879 ignore := true
880 switch msg.Command {
881 case "PRIVMSG", "NOTICE":
882 ignore = false
883 }
884 if ignore {
885 continue
886 }
887
888 if dc.caps["batch"] {
889 msg = msg.Copy()
890 msg.Tags["batch"] = irc.TagValue(batchRef)
891 }
892
893 dc.SendMessage(dc.marshalMessage(msg, net))
894 }
895
896 if dc.caps["batch"] {
897 dc.SendMessage(&irc.Message{
898 Prefix: dc.srv.prefix(),
899 Command: "BATCH",
900 Params: []string{"-" + batchRef},
901 })
902 }
903 }
904}
905
906func (dc *downstreamConn) runUntilRegistered() error {
907 for !dc.registered {
908 msg, err := dc.ReadMessage()
909 if err != nil {
910 return fmt.Errorf("failed to read IRC command: %v", err)
911 }
912
913 err = dc.handleMessage(msg)
914 if ircErr, ok := err.(ircError); ok {
915 ircErr.Message.Prefix = dc.srv.prefix()
916 dc.SendMessage(ircErr.Message)
917 } else if err != nil {
918 return fmt.Errorf("failed to handle IRC command %q: %v", msg, err)
919 }
920 }
921
922 return nil
923}
924
925func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
926 switch msg.Command {
927 case "CAP":
928 var subCmd string
929 if err := parseMessageParams(msg, &subCmd); err != nil {
930 return err
931 }
932 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
933 return err
934 }
935 case "PING":
936 dc.SendMessage(&irc.Message{
937 Prefix: dc.srv.prefix(),
938 Command: "PONG",
939 Params: msg.Params,
940 })
941 return nil
942 case "USER":
943 return ircError{&irc.Message{
944 Command: irc.ERR_ALREADYREGISTERED,
945 Params: []string{dc.nick, "You may not reregister"},
946 }}
947 case "NICK":
948 var nick string
949 if err := parseMessageParams(msg, &nick); err != nil {
950 return err
951 }
952
953 var upstream *upstreamConn
954 if dc.upstream() == nil {
955 uc, unmarshaledNick, err := dc.unmarshalEntity(nick)
956 if err == nil { // NICK nick/network: NICK only on a specific upstream
957 upstream = uc
958 nick = unmarshaledNick
959 }
960 }
961
962 var err error
963 dc.forEachNetwork(func(n *network) {
964 if err != nil || (upstream != nil && upstream.network != n) {
965 return
966 }
967 n.Nick = nick
968 err = dc.srv.db.StoreNetwork(dc.user.Username, &n.Network)
969 })
970 if err != nil {
971 return err
972 }
973
974 dc.forEachUpstream(func(uc *upstreamConn) {
975 if upstream != nil && upstream != uc {
976 return
977 }
978 uc.SendMessageLabeled(dc.id, &irc.Message{
979 Command: "NICK",
980 Params: []string{nick},
981 })
982 })
983
984 if dc.upstream() == nil && dc.nick != nick {
985 dc.SendMessage(&irc.Message{
986 Prefix: dc.prefix(),
987 Command: "NICK",
988 Params: []string{nick},
989 })
990 dc.nick = nick
991 }
992 case "JOIN":
993 var namesStr string
994 if err := parseMessageParams(msg, &namesStr); err != nil {
995 return err
996 }
997
998 var keys []string
999 if len(msg.Params) > 1 {
1000 keys = strings.Split(msg.Params[1], ",")
1001 }
1002
1003 for i, name := range strings.Split(namesStr, ",") {
1004 uc, upstreamName, err := dc.unmarshalEntity(name)
1005 if err != nil {
1006 return err
1007 }
1008
1009 var key string
1010 if len(keys) > i {
1011 key = keys[i]
1012 }
1013
1014 params := []string{upstreamName}
1015 if key != "" {
1016 params = append(params, key)
1017 }
1018 uc.SendMessageLabeled(dc.id, &irc.Message{
1019 Command: "JOIN",
1020 Params: params,
1021 })
1022
1023 ch := &Channel{Name: upstreamName, Key: key, Detached: false}
1024 if current, ok := uc.network.channels[ch.Name]; ok && key == "" {
1025 // Don't clear the channel key if there's one set
1026 // TODO: add a way to unset the channel key
1027 ch.Key = current.Key
1028 }
1029 if err := uc.network.createUpdateChannel(ch); err != nil {
1030 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1031 }
1032 }
1033 case "PART":
1034 var namesStr string
1035 if err := parseMessageParams(msg, &namesStr); err != nil {
1036 return err
1037 }
1038
1039 var reason string
1040 if len(msg.Params) > 1 {
1041 reason = msg.Params[1]
1042 }
1043
1044 for _, name := range strings.Split(namesStr, ",") {
1045 uc, upstreamName, err := dc.unmarshalEntity(name)
1046 if err != nil {
1047 return err
1048 }
1049
1050 if strings.EqualFold(reason, "detach") {
1051 ch := &Channel{Name: upstreamName, Detached: true}
1052 if err := uc.network.createUpdateChannel(ch); err != nil {
1053 dc.logger.Printf("failed to detach channel %q: %v", upstreamName, err)
1054 }
1055 } else {
1056 params := []string{upstreamName}
1057 if reason != "" {
1058 params = append(params, reason)
1059 }
1060 uc.SendMessageLabeled(dc.id, &irc.Message{
1061 Command: "PART",
1062 Params: params,
1063 })
1064
1065 if err := uc.network.deleteChannel(upstreamName); err != nil {
1066 dc.logger.Printf("failed to delete channel %q: %v", upstreamName, err)
1067 }
1068 }
1069 }
1070 case "KICK":
1071 var channelStr, userStr string
1072 if err := parseMessageParams(msg, &channelStr, &userStr); err != nil {
1073 return err
1074 }
1075
1076 channels := strings.Split(channelStr, ",")
1077 users := strings.Split(userStr, ",")
1078
1079 var reason string
1080 if len(msg.Params) > 2 {
1081 reason = msg.Params[2]
1082 }
1083
1084 if len(channels) != 1 && len(channels) != len(users) {
1085 return ircError{&irc.Message{
1086 Command: irc.ERR_BADCHANMASK,
1087 Params: []string{dc.nick, channelStr, "Bad channel mask"},
1088 }}
1089 }
1090
1091 for i, user := range users {
1092 var channel string
1093 if len(channels) == 1 {
1094 channel = channels[0]
1095 } else {
1096 channel = channels[i]
1097 }
1098
1099 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1100 if err != nil {
1101 return err
1102 }
1103
1104 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1105 if err != nil {
1106 return err
1107 }
1108
1109 if ucChannel != ucUser {
1110 return ircError{&irc.Message{
1111 Command: irc.ERR_USERNOTINCHANNEL,
1112 Params: []string{dc.nick, user, channel, "They aren't on that channel"},
1113 }}
1114 }
1115 uc := ucChannel
1116
1117 params := []string{upstreamChannel, upstreamUser}
1118 if reason != "" {
1119 params = append(params, reason)
1120 }
1121 uc.SendMessageLabeled(dc.id, &irc.Message{
1122 Command: "KICK",
1123 Params: params,
1124 })
1125 }
1126 case "MODE":
1127 var name string
1128 if err := parseMessageParams(msg, &name); err != nil {
1129 return err
1130 }
1131
1132 var modeStr string
1133 if len(msg.Params) > 1 {
1134 modeStr = msg.Params[1]
1135 }
1136
1137 if name == dc.nick {
1138 if modeStr != "" {
1139 dc.forEachUpstream(func(uc *upstreamConn) {
1140 uc.SendMessageLabeled(dc.id, &irc.Message{
1141 Command: "MODE",
1142 Params: []string{uc.nick, modeStr},
1143 })
1144 })
1145 } else {
1146 dc.SendMessage(&irc.Message{
1147 Prefix: dc.srv.prefix(),
1148 Command: irc.RPL_UMODEIS,
1149 Params: []string{dc.nick, ""}, // TODO
1150 })
1151 }
1152 return nil
1153 }
1154
1155 uc, upstreamName, err := dc.unmarshalEntity(name)
1156 if err != nil {
1157 return err
1158 }
1159
1160 if !uc.isChannel(upstreamName) {
1161 return ircError{&irc.Message{
1162 Command: irc.ERR_USERSDONTMATCH,
1163 Params: []string{dc.nick, "Cannot change mode for other users"},
1164 }}
1165 }
1166
1167 if modeStr != "" {
1168 params := []string{upstreamName, modeStr}
1169 params = append(params, msg.Params[2:]...)
1170 uc.SendMessageLabeled(dc.id, &irc.Message{
1171 Command: "MODE",
1172 Params: params,
1173 })
1174 } else {
1175 ch, ok := uc.channels[upstreamName]
1176 if !ok {
1177 return ircError{&irc.Message{
1178 Command: irc.ERR_NOSUCHCHANNEL,
1179 Params: []string{dc.nick, name, "No such channel"},
1180 }}
1181 }
1182
1183 if ch.modes == nil {
1184 // we haven't received the initial RPL_CHANNELMODEIS yet
1185 // ignore the request, we will broadcast the modes later when we receive RPL_CHANNELMODEIS
1186 return nil
1187 }
1188
1189 modeStr, modeParams := ch.modes.Format()
1190 params := []string{dc.nick, name, modeStr}
1191 params = append(params, modeParams...)
1192
1193 dc.SendMessage(&irc.Message{
1194 Prefix: dc.srv.prefix(),
1195 Command: irc.RPL_CHANNELMODEIS,
1196 Params: params,
1197 })
1198 if ch.creationTime != "" {
1199 dc.SendMessage(&irc.Message{
1200 Prefix: dc.srv.prefix(),
1201 Command: rpl_creationtime,
1202 Params: []string{dc.nick, name, ch.creationTime},
1203 })
1204 }
1205 }
1206 case "TOPIC":
1207 var channel string
1208 if err := parseMessageParams(msg, &channel); err != nil {
1209 return err
1210 }
1211
1212 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1213 if err != nil {
1214 return err
1215 }
1216
1217 if len(msg.Params) > 1 { // setting topic
1218 topic := msg.Params[1]
1219 uc.SendMessageLabeled(dc.id, &irc.Message{
1220 Command: "TOPIC",
1221 Params: []string{upstreamChannel, topic},
1222 })
1223 } else { // getting topic
1224 ch, ok := uc.channels[upstreamChannel]
1225 if !ok {
1226 return ircError{&irc.Message{
1227 Command: irc.ERR_NOSUCHCHANNEL,
1228 Params: []string{dc.nick, upstreamChannel, "No such channel"},
1229 }}
1230 }
1231 sendTopic(dc, ch)
1232 }
1233 case "LIST":
1234 // TODO: support ELIST when supported by all upstreams
1235
1236 pl := pendingLIST{
1237 downstreamID: dc.id,
1238 pendingCommands: make(map[int64]*irc.Message),
1239 }
1240 var upstream *upstreamConn
1241 var upstreamChannels map[int64][]string
1242 if len(msg.Params) > 0 {
1243 uc, upstreamMask, err := dc.unmarshalEntity(msg.Params[0])
1244 if err == nil && upstreamMask == "*" { // LIST */network: send LIST only to one network
1245 upstream = uc
1246 } else {
1247 upstreamChannels = make(map[int64][]string)
1248 channels := strings.Split(msg.Params[0], ",")
1249 for _, channel := range channels {
1250 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1251 if err != nil {
1252 return err
1253 }
1254 upstreamChannels[uc.network.ID] = append(upstreamChannels[uc.network.ID], upstreamChannel)
1255 }
1256 }
1257 }
1258
1259 dc.user.pendingLISTs = append(dc.user.pendingLISTs, pl)
1260 dc.forEachUpstream(func(uc *upstreamConn) {
1261 if upstream != nil && upstream != uc {
1262 return
1263 }
1264 var params []string
1265 if upstreamChannels != nil {
1266 if channels, ok := upstreamChannels[uc.network.ID]; ok {
1267 params = []string{strings.Join(channels, ",")}
1268 } else {
1269 return
1270 }
1271 }
1272 pl.pendingCommands[uc.network.ID] = &irc.Message{
1273 Command: "LIST",
1274 Params: params,
1275 }
1276 uc.trySendLIST(dc.id)
1277 })
1278 case "NAMES":
1279 if len(msg.Params) == 0 {
1280 dc.SendMessage(&irc.Message{
1281 Prefix: dc.srv.prefix(),
1282 Command: irc.RPL_ENDOFNAMES,
1283 Params: []string{dc.nick, "*", "End of /NAMES list"},
1284 })
1285 return nil
1286 }
1287
1288 channels := strings.Split(msg.Params[0], ",")
1289 for _, channel := range channels {
1290 uc, upstreamChannel, err := dc.unmarshalEntity(channel)
1291 if err != nil {
1292 return err
1293 }
1294
1295 ch, ok := uc.channels[upstreamChannel]
1296 if ok {
1297 sendNames(dc, ch)
1298 } else {
1299 // NAMES on a channel we have not joined, ask upstream
1300 uc.SendMessageLabeled(dc.id, &irc.Message{
1301 Command: "NAMES",
1302 Params: []string{upstreamChannel},
1303 })
1304 }
1305 }
1306 case "WHO":
1307 if len(msg.Params) == 0 {
1308 // TODO: support WHO without parameters
1309 dc.SendMessage(&irc.Message{
1310 Prefix: dc.srv.prefix(),
1311 Command: irc.RPL_ENDOFWHO,
1312 Params: []string{dc.nick, "*", "End of /WHO list"},
1313 })
1314 return nil
1315 }
1316
1317 // TODO: support WHO masks
1318 entity := msg.Params[0]
1319
1320 if entity == dc.nick {
1321 // TODO: support AWAY (H/G) in self WHO reply
1322 dc.SendMessage(&irc.Message{
1323 Prefix: dc.srv.prefix(),
1324 Command: irc.RPL_WHOREPLY,
1325 Params: []string{dc.nick, "*", dc.user.Username, dc.hostname, dc.srv.Hostname, dc.nick, "H", "0 " + dc.realname},
1326 })
1327 dc.SendMessage(&irc.Message{
1328 Prefix: dc.srv.prefix(),
1329 Command: irc.RPL_ENDOFWHO,
1330 Params: []string{dc.nick, dc.nick, "End of /WHO list"},
1331 })
1332 return nil
1333 }
1334
1335 uc, upstreamName, err := dc.unmarshalEntity(entity)
1336 if err != nil {
1337 return err
1338 }
1339
1340 var params []string
1341 if len(msg.Params) == 2 {
1342 params = []string{upstreamName, msg.Params[1]}
1343 } else {
1344 params = []string{upstreamName}
1345 }
1346
1347 uc.SendMessageLabeled(dc.id, &irc.Message{
1348 Command: "WHO",
1349 Params: params,
1350 })
1351 case "WHOIS":
1352 if len(msg.Params) == 0 {
1353 return ircError{&irc.Message{
1354 Command: irc.ERR_NONICKNAMEGIVEN,
1355 Params: []string{dc.nick, "No nickname given"},
1356 }}
1357 }
1358
1359 var target, mask string
1360 if len(msg.Params) == 1 {
1361 target = ""
1362 mask = msg.Params[0]
1363 } else {
1364 target = msg.Params[0]
1365 mask = msg.Params[1]
1366 }
1367 // TODO: support multiple WHOIS users
1368 if i := strings.IndexByte(mask, ','); i >= 0 {
1369 mask = mask[:i]
1370 }
1371
1372 if mask == dc.nick {
1373 dc.SendMessage(&irc.Message{
1374 Prefix: dc.srv.prefix(),
1375 Command: irc.RPL_WHOISUSER,
1376 Params: []string{dc.nick, dc.nick, dc.user.Username, dc.hostname, "*", dc.realname},
1377 })
1378 dc.SendMessage(&irc.Message{
1379 Prefix: dc.srv.prefix(),
1380 Command: irc.RPL_WHOISSERVER,
1381 Params: []string{dc.nick, dc.nick, dc.srv.Hostname, "soju"},
1382 })
1383 dc.SendMessage(&irc.Message{
1384 Prefix: dc.srv.prefix(),
1385 Command: irc.RPL_ENDOFWHOIS,
1386 Params: []string{dc.nick, dc.nick, "End of /WHOIS list"},
1387 })
1388 return nil
1389 }
1390
1391 // TODO: support WHOIS masks
1392 uc, upstreamNick, err := dc.unmarshalEntity(mask)
1393 if err != nil {
1394 return err
1395 }
1396
1397 var params []string
1398 if target != "" {
1399 if target == mask { // WHOIS nick nick
1400 params = []string{upstreamNick, upstreamNick}
1401 } else {
1402 params = []string{target, upstreamNick}
1403 }
1404 } else {
1405 params = []string{upstreamNick}
1406 }
1407
1408 uc.SendMessageLabeled(dc.id, &irc.Message{
1409 Command: "WHOIS",
1410 Params: params,
1411 })
1412 case "PRIVMSG":
1413 var targetsStr, text string
1414 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
1415 return err
1416 }
1417 tags := copyClientTags(msg.Tags)
1418
1419 for _, name := range strings.Split(targetsStr, ",") {
1420 if name == serviceNick {
1421 handleServicePRIVMSG(dc, text)
1422 continue
1423 }
1424
1425 uc, upstreamName, err := dc.unmarshalEntity(name)
1426 if err != nil {
1427 return err
1428 }
1429
1430 if upstreamName == "NickServ" {
1431 dc.handleNickServPRIVMSG(uc, text)
1432 }
1433
1434 unmarshaledText := text
1435 if uc.isChannel(upstreamName) {
1436 unmarshaledText = dc.unmarshalText(uc, text)
1437 }
1438 uc.SendMessageLabeled(dc.id, &irc.Message{
1439 Tags: tags,
1440 Command: "PRIVMSG",
1441 Params: []string{upstreamName, unmarshaledText},
1442 })
1443
1444 echoTags := tags.Copy()
1445 echoTags["time"] = irc.TagValue(time.Now().UTC().Format(serverTimeLayout))
1446 echoMsg := &irc.Message{
1447 Tags: echoTags,
1448 Prefix: &irc.Prefix{
1449 Name: uc.nick,
1450 User: uc.username,
1451 },
1452 Command: "PRIVMSG",
1453 Params: []string{upstreamName, text},
1454 }
1455 uc.produce(upstreamName, echoMsg, dc)
1456 }
1457 case "NOTICE":
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 uc, upstreamName, err := dc.unmarshalEntity(name)
1466 if err != nil {
1467 return err
1468 }
1469
1470 unmarshaledText := text
1471 if uc.isChannel(upstreamName) {
1472 unmarshaledText = dc.unmarshalText(uc, text)
1473 }
1474 uc.SendMessageLabeled(dc.id, &irc.Message{
1475 Tags: tags,
1476 Command: "NOTICE",
1477 Params: []string{upstreamName, unmarshaledText},
1478 })
1479 }
1480 case "TAGMSG":
1481 var targetsStr string
1482 if err := parseMessageParams(msg, &targetsStr); err != nil {
1483 return err
1484 }
1485 tags := copyClientTags(msg.Tags)
1486
1487 for _, name := range strings.Split(targetsStr, ",") {
1488 uc, upstreamName, err := dc.unmarshalEntity(name)
1489 if err != nil {
1490 return err
1491 }
1492
1493 uc.SendMessageLabeled(dc.id, &irc.Message{
1494 Tags: tags,
1495 Command: "TAGMSG",
1496 Params: []string{upstreamName},
1497 })
1498 }
1499 case "INVITE":
1500 var user, channel string
1501 if err := parseMessageParams(msg, &user, &channel); err != nil {
1502 return err
1503 }
1504
1505 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1506 if err != nil {
1507 return err
1508 }
1509
1510 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1511 if err != nil {
1512 return err
1513 }
1514
1515 if ucChannel != ucUser {
1516 return ircError{&irc.Message{
1517 Command: irc.ERR_USERNOTINCHANNEL,
1518 Params: []string{dc.nick, user, channel, "They aren't on that channel"},
1519 }}
1520 }
1521 uc := ucChannel
1522
1523 uc.SendMessageLabeled(dc.id, &irc.Message{
1524 Command: "INVITE",
1525 Params: []string{upstreamUser, upstreamChannel},
1526 })
1527 case "CHATHISTORY":
1528 var subcommand string
1529 if err := parseMessageParams(msg, &subcommand); err != nil {
1530 return err
1531 }
1532 var target, criteria, limitStr string
1533 if err := parseMessageParams(msg, nil, &target, &criteria, &limitStr); err != nil {
1534 return ircError{&irc.Message{
1535 Command: "FAIL",
1536 Params: []string{"CHATHISTORY", "NEED_MORE_PARAMS", subcommand, "Missing parameters"},
1537 }}
1538 }
1539
1540 if dc.srv.LogPath == "" {
1541 return ircError{&irc.Message{
1542 Command: irc.ERR_UNKNOWNCOMMAND,
1543 Params: []string{dc.nick, subcommand, "Unknown command"},
1544 }}
1545 }
1546
1547 uc, entity, err := dc.unmarshalEntity(target)
1548 if err != nil {
1549 return err
1550 }
1551
1552 // TODO: support msgid criteria
1553 criteriaParts := strings.SplitN(criteria, "=", 2)
1554 if len(criteriaParts) != 2 || criteriaParts[0] != "timestamp" {
1555 return ircError{&irc.Message{
1556 Command: "FAIL",
1557 Params: []string{"CHATHISTORY", "UNKNOWN_CRITERIA", criteria, "Unknown criteria"},
1558 }}
1559 }
1560
1561 timestamp, err := time.Parse(serverTimeLayout, criteriaParts[1])
1562 if err != nil {
1563 return ircError{&irc.Message{
1564 Command: "FAIL",
1565 Params: []string{"CHATHISTORY", "INVALID_CRITERIA", criteria, "Invalid criteria"},
1566 }}
1567 }
1568
1569 limit, err := strconv.Atoi(limitStr)
1570 if err != nil || limit < 0 || limit > dc.srv.HistoryLimit {
1571 return ircError{&irc.Message{
1572 Command: "FAIL",
1573 Params: []string{"CHATHISTORY", "INVALID_LIMIT", limitStr, "Invalid limit"},
1574 }}
1575 }
1576
1577 switch subcommand {
1578 case "BEFORE":
1579 batchRef := "history"
1580 dc.SendMessage(&irc.Message{
1581 Prefix: dc.srv.prefix(),
1582 Command: "BATCH",
1583 Params: []string{"+" + batchRef, "chathistory", target},
1584 })
1585
1586 history := make([]*irc.Message, limit)
1587 remaining := limit
1588
1589 tries := 0
1590 for remaining > 0 {
1591 buf, err := parseMessagesBefore(uc.network, entity, timestamp, remaining)
1592 if err != nil {
1593 dc.logger.Printf("failed parsing log messages for chathistory: %v", err)
1594 return newChatHistoryError(subcommand, target)
1595 }
1596 if len(buf) == 0 {
1597 tries++
1598 if tries >= 100 {
1599 break
1600 }
1601 } else {
1602 tries = 0
1603 }
1604 copy(history[remaining-len(buf):], buf)
1605 remaining -= len(buf)
1606 year, month, day := timestamp.Date()
1607 timestamp = time.Date(year, month, day, 0, 0, 0, 0, timestamp.Location()).Add(-1)
1608 }
1609
1610 for _, m := range history[remaining:] {
1611 m.Tags["batch"] = irc.TagValue(batchRef)
1612 dc.SendMessage(dc.marshalMessage(m, uc.network))
1613 }
1614
1615 dc.SendMessage(&irc.Message{
1616 Prefix: dc.srv.prefix(),
1617 Command: "BATCH",
1618 Params: []string{"-" + batchRef},
1619 })
1620 default:
1621 // TODO: support AFTER, LATEST, BETWEEN
1622 return ircError{&irc.Message{
1623 Command: "FAIL",
1624 Params: []string{"CHATHISTORY", "UNKNOWN_COMMAND", subcommand, "Unknown command"},
1625 }}
1626 }
1627 default:
1628 dc.logger.Printf("unhandled message: %v", msg)
1629 return newUnknownCommandError(msg.Command)
1630 }
1631 return nil
1632}
1633
1634func (dc *downstreamConn) handleNickServPRIVMSG(uc *upstreamConn, text string) {
1635 username, password, ok := parseNickServCredentials(text, uc.nick)
1636 if !ok {
1637 return
1638 }
1639
1640 // User may have e.g. EXTERNAL mechanism configured. We do not want to
1641 // automatically erase the key pair or any other credentials.
1642 if uc.network.SASL.Mechanism != "" && uc.network.SASL.Mechanism != "PLAIN" {
1643 return
1644 }
1645
1646 dc.logger.Printf("auto-saving NickServ credentials with username %q", username)
1647 n := uc.network
1648 n.SASL.Mechanism = "PLAIN"
1649 n.SASL.Plain.Username = username
1650 n.SASL.Plain.Password = password
1651 if err := dc.srv.db.StoreNetwork(dc.user.Username, &n.Network); err != nil {
1652 dc.logger.Printf("failed to save NickServ credentials: %v", err)
1653 }
1654}
1655
1656func parseNickServCredentials(text, nick string) (username, password string, ok bool) {
1657 fields := strings.Fields(text)
1658 if len(fields) < 2 {
1659 return "", "", false
1660 }
1661 cmd := strings.ToUpper(fields[0])
1662 params := fields[1:]
1663 switch cmd {
1664 case "REGISTER":
1665 username = nick
1666 password = params[0]
1667 case "IDENTIFY":
1668 if len(params) == 1 {
1669 username = nick
1670 password = params[0]
1671 } else {
1672 username = params[0]
1673 password = params[1]
1674 }
1675 case "SET":
1676 if len(params) == 2 && strings.EqualFold(params[0], "PASSWORD") {
1677 username = nick
1678 password = params[1]
1679 }
1680 }
1681 return username, password, true
1682}
Note: See TracBrowser for help on using the repository browser.