source: code/trunk/downstream.go@ 425

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

Add message store abstraction

Introduce a messageStore type, which will allow for multiple
implementations (e.g. in the DB or in-memory instead of on-disk).

The message store is per-user so that we don't need to deal with locking
and it's easier to implement per-user limits.

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