source: code/trunk/downstream.go@ 427

Last change on this file since 427 was 427, checked in by hubert, 5 years ago

Don't send TAGMSG to upstreams that don't support it

TAGMSG are (in current specs and drafts from IRCv3) only used for
client tags. These are optional information by design (since they are
not distributed to all users), therefore it is preferable to discard
them accordingly to upstream, instead of waiting for all upstreams to
support the capability to advertise it.

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 }
[427]1556 if _, ok := uc.caps["message-tags"]; !ok {
1557 continue
1558 }
[303]1559
1560 uc.SendMessageLabeled(dc.id, &irc.Message{
1561 Tags: tags,
1562 Command: "TAGMSG",
1563 Params: []string{upstreamName},
1564 })
1565 }
[163]1566 case "INVITE":
1567 var user, channel string
1568 if err := parseMessageParams(msg, &user, &channel); err != nil {
1569 return err
1570 }
1571
1572 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1573 if err != nil {
1574 return err
1575 }
1576
1577 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1578 if err != nil {
1579 return err
1580 }
1581
1582 if ucChannel != ucUser {
1583 return ircError{&irc.Message{
1584 Command: irc.ERR_USERNOTINCHANNEL,
[401]1585 Params: []string{dc.nick, user, channel, "They are on another network"},
[163]1586 }}
1587 }
1588 uc := ucChannel
1589
[176]1590 uc.SendMessageLabeled(dc.id, &irc.Message{
[163]1591 Command: "INVITE",
1592 Params: []string{upstreamUser, upstreamChannel},
1593 })
[319]1594 case "CHATHISTORY":
1595 var subcommand string
1596 if err := parseMessageParams(msg, &subcommand); err != nil {
1597 return err
1598 }
1599 var target, criteria, limitStr string
1600 if err := parseMessageParams(msg, nil, &target, &criteria, &limitStr); err != nil {
1601 return ircError{&irc.Message{
1602 Command: "FAIL",
1603 Params: []string{"CHATHISTORY", "NEED_MORE_PARAMS", subcommand, "Missing parameters"},
1604 }}
1605 }
1606
[423]1607 if dc.user.msgStore == nil {
[319]1608 return ircError{&irc.Message{
1609 Command: irc.ERR_UNKNOWNCOMMAND,
1610 Params: []string{dc.nick, subcommand, "Unknown command"},
1611 }}
1612 }
1613
1614 uc, entity, err := dc.unmarshalEntity(target)
1615 if err != nil {
1616 return err
1617 }
1618
1619 // TODO: support msgid criteria
1620 criteriaParts := strings.SplitN(criteria, "=", 2)
1621 if len(criteriaParts) != 2 || criteriaParts[0] != "timestamp" {
1622 return ircError{&irc.Message{
1623 Command: "FAIL",
1624 Params: []string{"CHATHISTORY", "UNKNOWN_CRITERIA", criteria, "Unknown criteria"},
1625 }}
1626 }
1627
1628 timestamp, err := time.Parse(serverTimeLayout, criteriaParts[1])
1629 if err != nil {
1630 return ircError{&irc.Message{
1631 Command: "FAIL",
1632 Params: []string{"CHATHISTORY", "INVALID_CRITERIA", criteria, "Invalid criteria"},
1633 }}
1634 }
1635
1636 limit, err := strconv.Atoi(limitStr)
1637 if err != nil || limit < 0 || limit > dc.srv.HistoryLimit {
1638 return ircError{&irc.Message{
1639 Command: "FAIL",
1640 Params: []string{"CHATHISTORY", "INVALID_LIMIT", limitStr, "Invalid limit"},
1641 }}
1642 }
1643
[387]1644 var history []*irc.Message
[319]1645 switch subcommand {
1646 case "BEFORE":
[423]1647 history, err = dc.user.msgStore.LoadBeforeTime(uc.network, entity, timestamp, limit)
[360]1648 case "AFTER":
[423]1649 history, err = dc.user.msgStore.LoadAfterTime(uc.network, entity, timestamp, limit)
[319]1650 default:
[360]1651 // TODO: support LATEST, BETWEEN
[319]1652 return ircError{&irc.Message{
1653 Command: "FAIL",
1654 Params: []string{"CHATHISTORY", "UNKNOWN_COMMAND", subcommand, "Unknown command"},
1655 }}
1656 }
[387]1657 if err != nil {
1658 dc.logger.Printf("failed parsing log messages for chathistory: %v", err)
1659 return newChatHistoryError(subcommand, target)
1660 }
1661
1662 batchRef := "history"
1663 dc.SendMessage(&irc.Message{
1664 Prefix: dc.srv.prefix(),
1665 Command: "BATCH",
1666 Params: []string{"+" + batchRef, "chathistory", target},
1667 })
1668
1669 for _, msg := range history {
1670 msg.Tags["batch"] = irc.TagValue(batchRef)
1671 dc.SendMessage(dc.marshalMessage(msg, uc.network))
1672 }
1673
1674 dc.SendMessage(&irc.Message{
1675 Prefix: dc.srv.prefix(),
1676 Command: "BATCH",
1677 Params: []string{"-" + batchRef},
1678 })
[13]1679 default:
[55]1680 dc.logger.Printf("unhandled message: %v", msg)
[13]1681 return newUnknownCommandError(msg.Command)
1682 }
[42]1683 return nil
[13]1684}
[95]1685
1686func (dc *downstreamConn) handleNickServPRIVMSG(uc *upstreamConn, text string) {
1687 username, password, ok := parseNickServCredentials(text, uc.nick)
1688 if !ok {
1689 return
1690 }
1691
[307]1692 // User may have e.g. EXTERNAL mechanism configured. We do not want to
1693 // automatically erase the key pair or any other credentials.
1694 if uc.network.SASL.Mechanism != "" && uc.network.SASL.Mechanism != "PLAIN" {
1695 return
1696 }
1697
[95]1698 dc.logger.Printf("auto-saving NickServ credentials with username %q", username)
1699 n := uc.network
1700 n.SASL.Mechanism = "PLAIN"
1701 n.SASL.Plain.Username = username
1702 n.SASL.Plain.Password = password
[421]1703 if err := dc.srv.db.StoreNetwork(dc.user.ID, &n.Network); err != nil {
[95]1704 dc.logger.Printf("failed to save NickServ credentials: %v", err)
1705 }
1706}
1707
1708func parseNickServCredentials(text, nick string) (username, password string, ok bool) {
1709 fields := strings.Fields(text)
1710 if len(fields) < 2 {
1711 return "", "", false
1712 }
1713 cmd := strings.ToUpper(fields[0])
1714 params := fields[1:]
1715 switch cmd {
1716 case "REGISTER":
1717 username = nick
1718 password = params[0]
1719 case "IDENTIFY":
1720 if len(params) == 1 {
1721 username = nick
[182]1722 password = params[0]
[95]1723 } else {
1724 username = params[0]
[182]1725 password = params[1]
[95]1726 }
[182]1727 case "SET":
1728 if len(params) == 2 && strings.EqualFold(params[0], "PASSWORD") {
1729 username = nick
1730 password = params[1]
1731 }
[340]1732 default:
1733 return "", "", false
[95]1734 }
1735 return username, password, true
1736}
Note: See TracBrowser for help on using the repository browser.