source: code/trunk/downstream.go@ 530

Last change on this file since 530 was 529, checked in by philipk, 4 years ago

Directly return self-messages to user in multi-upstream mode

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