source: code/trunk/downstream.go@ 788

Last change on this file since 788 was 788, checked in by contact, 3 years ago

downstream: panic when registering twice

This would be a soju bug.

File size: 77.6 KB
RevLine 
[98]1package soju
[13]2
3import (
[760]4 "bytes"
[652]5 "context"
[91]6 "crypto/tls"
[112]7 "encoding/base64"
[655]8 "errors"
[13]9 "fmt"
10 "io"
11 "net"
[108]12 "strconv"
[39]13 "strings"
[91]14 "time"
[13]15
[112]16 "github.com/emersion/go-sasl"
[85]17 "golang.org/x/crypto/bcrypt"
[13]18 "gopkg.in/irc.v3"
19)
20
21type ircError struct {
22 Message *irc.Message
23}
24
[85]25func (err ircError) Error() string {
26 return err.Message.String()
27}
28
[13]29func newUnknownCommandError(cmd string) ircError {
30 return ircError{&irc.Message{
31 Command: irc.ERR_UNKNOWNCOMMAND,
32 Params: []string{
33 "*",
34 cmd,
35 "Unknown command",
36 },
37 }}
38}
39
40func newNeedMoreParamsError(cmd string) ircError {
41 return ircError{&irc.Message{
42 Command: irc.ERR_NEEDMOREPARAMS,
43 Params: []string{
44 "*",
45 cmd,
46 "Not enough parameters",
47 },
48 }}
49}
50
[319]51func newChatHistoryError(subcommand string, target string) ircError {
52 return ircError{&irc.Message{
53 Command: "FAIL",
54 Params: []string{"CHATHISTORY", "MESSAGE_ERROR", subcommand, target, "Messages could not be retrieved"},
55 }}
56}
57
[726]58// authError is an authentication error.
59type authError struct {
60 // Internal error cause. This will not be revealed to the user.
61 err error
62 // Error cause which can safely be sent to the user without compromising
63 // security.
64 reason string
65}
[13]66
[726]67func (err *authError) Error() string {
68 return err.err.Error()
69}
70
71func (err *authError) Unwrap() error {
72 return err.err
73}
74
75// authErrorReason returns the user-friendly reason of an authentication
76// failure.
77func authErrorReason(err error) string {
78 if authErr, ok := err.(*authError); ok {
79 return authErr.reason
80 } else {
81 return "Authentication failed"
82 }
83}
84
85func newInvalidUsernameOrPasswordError(err error) error {
86 return &authError{
87 err: err,
88 reason: "Invalid username or password",
89 }
90}
91
[535]92func parseBouncerNetID(subcommand, s string) (int64, error) {
[532]93 id, err := strconv.ParseInt(s, 10, 64)
94 if err != nil {
95 return 0, ircError{&irc.Message{
96 Command: "FAIL",
[535]97 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, s, "Invalid network ID"},
[532]98 }}
99 }
100 return id, nil
101}
102
[654]103func fillNetworkAddrAttrs(attrs irc.Tags, network *Network) {
104 u, err := network.URL()
105 if err != nil {
106 return
107 }
108
109 hasHostPort := true
110 switch u.Scheme {
111 case "ircs":
112 attrs["tls"] = irc.TagValue("1")
113 case "irc+insecure":
114 attrs["tls"] = irc.TagValue("0")
115 default: // e.g. unix://
116 hasHostPort = false
117 }
118 if host, port, err := net.SplitHostPort(u.Host); err == nil && hasHostPort {
119 attrs["host"] = irc.TagValue(host)
120 attrs["port"] = irc.TagValue(port)
121 } else if hasHostPort {
122 attrs["host"] = irc.TagValue(u.Host)
123 }
124}
125
[535]126func getNetworkAttrs(network *network) irc.Tags {
127 state := "disconnected"
128 if uc := network.conn; uc != nil {
129 state = "connected"
130 }
131
132 attrs := irc.Tags{
133 "name": irc.TagValue(network.GetName()),
134 "state": irc.TagValue(state),
[664]135 "nickname": irc.TagValue(GetNick(&network.user.User, &network.Network)),
[535]136 }
137
138 if network.Username != "" {
139 attrs["username"] = irc.TagValue(network.Username)
140 }
[568]141 if realname := GetRealname(&network.user.User, &network.Network); realname != "" {
142 attrs["realname"] = irc.TagValue(realname)
[535]143 }
144
[654]145 fillNetworkAddrAttrs(attrs, &network.Network)
146
147 return attrs
148}
149
150func networkAddrFromAttrs(attrs irc.Tags) string {
151 host, ok := attrs.GetTag("host")
152 if !ok {
153 return ""
154 }
155
156 addr := host
157 if port, ok := attrs.GetTag("port"); ok {
158 addr += ":" + port
159 }
160
161 if tlsStr, ok := attrs.GetTag("tls"); ok && tlsStr == "0" {
162 addr = "irc+insecure://" + tlsStr
163 }
164
165 return addr
166}
167
168func updateNetworkAttrs(record *Network, attrs irc.Tags, subcommand string) error {
169 addrAttrs := irc.Tags{}
170 fillNetworkAddrAttrs(addrAttrs, record)
171
172 updateAddr := false
173 for k, v := range attrs {
174 s := string(v)
175 switch k {
176 case "host", "port", "tls":
177 updateAddr = true
178 addrAttrs[k] = v
179 case "name":
180 record.Name = s
181 case "nickname":
182 record.Nick = s
183 case "username":
184 record.Username = s
185 case "realname":
186 record.Realname = s
187 case "pass":
188 record.Pass = s
[535]189 default:
[654]190 return ircError{&irc.Message{
191 Command: "FAIL",
192 Params: []string{"BOUNCER", "UNKNOWN_ATTRIBUTE", subcommand, k, "Unknown attribute"},
193 }}
[535]194 }
[654]195 }
196
197 if updateAddr {
198 record.Addr = networkAddrFromAttrs(addrAttrs)
199 if record.Addr == "" {
200 return ircError{&irc.Message{
201 Command: "FAIL",
202 Params: []string{"BOUNCER", "NEED_ATTRIBUTE", subcommand, "host", "Missing required host attribute"},
203 }}
[535]204 }
205 }
206
[654]207 return nil
[535]208}
209
[411]210// ' ' and ':' break the IRC message wire format, '@' and '!' break prefixes,
[716]211// '*' and '?' break masks, '$' breaks server masks in PRIVMSG/NOTICE,
[751]212// "*" is the reserved nickname for registration, ',' breaks lists
213const illegalNickChars = " :@!*?$,"
[404]214
[275]215// permanentDownstreamCaps is the list of always-supported downstream
216// capabilities.
217var permanentDownstreamCaps = map[string]string{
[535]218 "batch": "",
219 "cap-notify": "",
220 "echo-message": "",
221 "invite-notify": "",
222 "message-tags": "",
223 "server-time": "",
[540]224 "setname": "",
[535]225
226 "soju.im/bouncer-networks": "",
227 "soju.im/bouncer-networks-notify": "",
[781]228 "soju.im/read": "",
[275]229}
230
[292]231// needAllDownstreamCaps is the list of downstream capabilities that
232// require support from all upstreams to be enabled
233var needAllDownstreamCaps = map[string]string{
[648]234 "account-notify": "",
235 "account-tag": "",
236 "away-notify": "",
237 "extended-join": "",
238 "multi-prefix": "",
[685]239
240 "draft/extended-monitor": "",
[292]241}
242
[463]243// passthroughIsupport is the set of ISUPPORT tokens that are directly passed
244// through from the upstream server to downstream clients.
245//
246// This is only effective in single-upstream mode.
247var passthroughIsupport = map[string]bool{
[580]248 "AWAYLEN": true,
249 "BOT": true,
250 "CHANLIMIT": true,
251 "CHANMODES": true,
252 "CHANNELLEN": true,
253 "CHANTYPES": true,
254 "CLIENTTAGDENY": true,
[683]255 "ELIST": true,
[580]256 "EXCEPTS": true,
257 "EXTBAN": true,
258 "HOSTLEN": true,
259 "INVEX": true,
260 "KICKLEN": true,
261 "MAXLIST": true,
262 "MAXTARGETS": true,
263 "MODES": true,
[684]264 "MONITOR": true,
[580]265 "NAMELEN": true,
266 "NETWORK": true,
267 "NICKLEN": true,
268 "PREFIX": true,
269 "SAFELIST": true,
270 "TARGMAX": true,
271 "TOPICLEN": true,
272 "USERLEN": true,
273 "UTF8ONLY": true,
[660]274 "WHOX": true,
[463]275}
276
[724]277type downstreamSASL struct {
278 server sasl.Server
279 plainUsername, plainPassword string
[760]280 pendingResp bytes.Buffer
[724]281}
282
[13]283type downstreamConn struct {
[210]284 conn
[22]285
[210]286 id uint64
287
[693]288 registered bool
289 user *user
290 nick string
291 nickCM string
292 rawUsername string
293 networkName string
294 clientName string
295 realname string
296 hostname string
[722]297 account string // RPL_LOGGEDIN/OUT state
[693]298 password string // empty after authentication
299 network *network // can be nil
300 isMultiUpstream bool
[105]301
[590]302 negotiatingCaps bool
[108]303 capVersion int
[275]304 supportedCaps map[string]string
[236]305 caps map[string]bool
[724]306 sasl *downstreamSASL
[108]307
[551]308 lastBatchRef uint64
309
[684]310 monitored casemapMap
[13]311}
312
[347]313func newDownstreamConn(srv *Server, ic ircConn, id uint64) *downstreamConn {
314 remoteAddr := ic.RemoteAddr().String()
[323]315 logger := &prefixLogger{srv.Logger, fmt.Sprintf("downstream %q: ", remoteAddr)}
[398]316 options := connOptions{Logger: logger}
[55]317 dc := &downstreamConn{
[398]318 conn: *newConn(srv, ic, &options),
[276]319 id: id,
[716]320 nick: "*",
321 nickCM: "*",
[275]322 supportedCaps: make(map[string]string),
[276]323 caps: make(map[string]bool),
[684]324 monitored: newCasemapMap(0),
[22]325 }
[323]326 dc.hostname = remoteAddr
[141]327 if host, _, err := net.SplitHostPort(dc.hostname); err == nil {
328 dc.hostname = host
329 }
[275]330 for k, v := range permanentDownstreamCaps {
331 dc.supportedCaps[k] = v
332 }
[725]333 dc.supportedCaps["sasl"] = "PLAIN"
[691]334 // TODO: this is racy, we should only enable chathistory after
335 // authentication and then check that user.msgStore implements
336 // chatHistoryMessageStore
337 if srv.Config().LogPath != "" {
[319]338 dc.supportedCaps["draft/chathistory"] = ""
339 }
[55]340 return dc
[22]341}
342
[55]343func (dc *downstreamConn) prefix() *irc.Prefix {
[27]344 return &irc.Prefix{
[55]345 Name: dc.nick,
[184]346 User: dc.user.Username,
[141]347 Host: dc.hostname,
[27]348 }
349}
350
[90]351func (dc *downstreamConn) forEachNetwork(f func(*network)) {
352 if dc.network != nil {
353 f(dc.network)
[693]354 } else if dc.isMultiUpstream {
[768]355 for _, network := range dc.user.networks {
356 f(network)
357 }
[90]358 }
359}
360
[73]361func (dc *downstreamConn) forEachUpstream(f func(*upstreamConn)) {
[693]362 if dc.network == nil && !dc.isMultiUpstream {
[532]363 return
364 }
[73]365 dc.user.forEachUpstream(func(uc *upstreamConn) {
[77]366 if dc.network != nil && uc.network != dc.network {
[73]367 return
368 }
369 f(uc)
370 })
371}
372
[89]373// upstream returns the upstream connection, if any. If there are zero or if
374// there are multiple upstream connections, it returns nil.
375func (dc *downstreamConn) upstream() *upstreamConn {
376 if dc.network == nil {
377 return nil
378 }
[279]379 return dc.network.conn
[89]380}
381
[260]382func isOurNick(net *network, nick string) bool {
383 // TODO: this doesn't account for nick changes
384 if net.conn != nil {
[478]385 return net.casemap(nick) == net.conn.nickCM
[260]386 }
387 // We're not currently connected to the upstream connection, so we don't
388 // know whether this name is our nickname. Best-effort: use the network's
389 // configured nickname and hope it was the one being used when we were
390 // connected.
[664]391 return net.casemap(nick) == net.casemap(GetNick(&net.user.User, &net.Network))
[260]392}
393
[249]394// marshalEntity converts an upstream entity name (ie. channel or nick) into a
395// downstream entity name.
396//
397// This involves adding a "/<network>" suffix if the entity isn't the current
398// user.
[260]399func (dc *downstreamConn) marshalEntity(net *network, name string) string {
[289]400 if isOurNick(net, name) {
401 return dc.nick
402 }
[478]403 name = partialCasemap(net.casemap, name)
[257]404 if dc.network != nil {
[260]405 if dc.network != net {
[258]406 panic("soju: tried to marshal an entity for another network")
407 }
[257]408 return name
[119]409 }
[260]410 return name + "/" + net.GetName()
[119]411}
412
[260]413func (dc *downstreamConn) marshalUserPrefix(net *network, prefix *irc.Prefix) *irc.Prefix {
414 if isOurNick(net, prefix.Name) {
[257]415 return dc.prefix()
416 }
[478]417 prefix.Name = partialCasemap(net.casemap, prefix.Name)
[130]418 if dc.network != nil {
[260]419 if dc.network != net {
[258]420 panic("soju: tried to marshal a user prefix for another network")
421 }
[257]422 return prefix
[119]423 }
[257]424 return &irc.Prefix{
[260]425 Name: prefix.Name + "/" + net.GetName(),
[257]426 User: prefix.User,
427 Host: prefix.Host,
428 }
[119]429}
430
[584]431// unmarshalEntityNetwork converts a downstream entity name (ie. channel or
432// nick) into an upstream entity name.
[249]433//
434// This involves removing the "/<network>" suffix.
[584]435func (dc *downstreamConn) unmarshalEntityNetwork(name string) (*network, string, error) {
[464]436 if dc.network != nil {
[584]437 return dc.network, name, nil
[464]438 }
[727]439 if !dc.isMultiUpstream {
440 return nil, "", ircError{&irc.Message{
441 Command: irc.ERR_NOSUCHCHANNEL,
442 Params: []string{dc.nick, name, "Cannot interact with channels and users on the bouncer connection. Did you mean to use a specific network?"},
443 }}
444 }
[89]445
[584]446 var net *network
[119]447 if i := strings.LastIndexByte(name, '/'); i >= 0 {
[127]448 network := name[i+1:]
[119]449 name = name[:i]
450
[584]451 for _, n := range dc.user.networks {
452 if network == n.GetName() {
453 net = n
454 break
[119]455 }
[584]456 }
[119]457 }
458
[584]459 if net == nil {
[73]460 return nil, "", ircError{&irc.Message{
461 Command: irc.ERR_NOSUCHCHANNEL,
[727]462 Params: []string{dc.nick, name, "Missing network suffix in name"},
[73]463 }}
[69]464 }
[584]465
466 return net, name, nil
[69]467}
468
[584]469// unmarshalEntity is the same as unmarshalEntityNetwork, but returns the
470// upstream connection and fails if the upstream is disconnected.
471func (dc *downstreamConn) unmarshalEntity(name string) (*upstreamConn, string, error) {
472 net, name, err := dc.unmarshalEntityNetwork(name)
473 if err != nil {
474 return nil, "", err
475 }
476
477 if net.conn == nil {
478 return nil, "", ircError{&irc.Message{
479 Command: irc.ERR_NOSUCHCHANNEL,
[727]480 Params: []string{dc.nick, name, "Disconnected from upstream network"},
[584]481 }}
482 }
483
484 return net.conn, name, nil
485}
486
[268]487func (dc *downstreamConn) unmarshalText(uc *upstreamConn, text string) string {
488 if dc.upstream() != nil {
489 return text
490 }
491 // TODO: smarter parsing that ignores URLs
492 return strings.ReplaceAll(text, "/"+uc.network.GetName(), "")
493}
494
[711]495func (dc *downstreamConn) ReadMessage() (*irc.Message, error) {
496 msg, err := dc.conn.ReadMessage()
497 if err != nil {
498 return nil, err
499 }
500 dc.srv.metrics.downstreamInMessagesTotal.Inc()
501 return msg, nil
502}
503
[165]504func (dc *downstreamConn) readMessages(ch chan<- event) error {
[22]505 for {
[210]506 msg, err := dc.ReadMessage()
[655]507 if errors.Is(err, io.EOF) {
[22]508 break
509 } else if err != nil {
510 return fmt.Errorf("failed to read IRC command: %v", err)
511 }
512
[165]513 ch <- eventDownstreamMessage{msg, dc}
[22]514 }
515
[45]516 return nil
[22]517}
518
[230]519// SendMessage sends an outgoing message.
520//
521// This can only called from the user goroutine.
[55]522func (dc *downstreamConn) SendMessage(msg *irc.Message) {
[230]523 if !dc.caps["message-tags"] {
[303]524 if msg.Command == "TAGMSG" {
525 return
526 }
[216]527 msg = msg.Copy()
528 for name := range msg.Tags {
529 supported := false
530 switch name {
531 case "time":
[230]532 supported = dc.caps["server-time"]
[559]533 case "account":
534 supported = dc.caps["account"]
[216]535 }
536 if !supported {
537 delete(msg.Tags, name)
538 }
539 }
540 }
[551]541 if !dc.caps["batch"] && msg.Tags["batch"] != "" {
542 msg = msg.Copy()
543 delete(msg.Tags, "batch")
544 }
[419]545 if msg.Command == "JOIN" && !dc.caps["extended-join"] {
546 msg.Params = msg.Params[:1]
547 }
[540]548 if msg.Command == "SETNAME" && !dc.caps["setname"] {
549 return
550 }
[649]551 if msg.Command == "AWAY" && !dc.caps["away-notify"] {
552 return
553 }
[648]554 if msg.Command == "ACCOUNT" && !dc.caps["account-notify"] {
555 return
556 }
[781]557 if msg.Command == "READ" && !dc.caps["soju.im/read"] {
558 return
559 }
[216]560
[711]561 dc.srv.metrics.downstreamOutMessagesTotal.Inc()
[757]562 dc.conn.SendMessage(context.TODO(), msg)
[54]563}
564
[551]565func (dc *downstreamConn) SendBatch(typ string, params []string, tags irc.Tags, f func(batchRef irc.TagValue)) {
566 dc.lastBatchRef++
567 ref := fmt.Sprintf("%v", dc.lastBatchRef)
568
569 if dc.caps["batch"] {
570 dc.SendMessage(&irc.Message{
571 Tags: tags,
572 Prefix: dc.srv.prefix(),
573 Command: "BATCH",
574 Params: append([]string{"+" + ref, typ}, params...),
575 })
576 }
577
578 f(irc.TagValue(ref))
579
580 if dc.caps["batch"] {
581 dc.SendMessage(&irc.Message{
582 Prefix: dc.srv.prefix(),
583 Command: "BATCH",
584 Params: []string{"-" + ref},
585 })
586 }
587}
588
[428]589// sendMessageWithID sends an outgoing message with the specified internal ID.
590func (dc *downstreamConn) sendMessageWithID(msg *irc.Message, id string) {
591 dc.SendMessage(msg)
592
[665]593 if id == "" || !dc.messageSupportsBacklog(msg) {
[428]594 return
595 }
596
597 dc.sendPing(id)
598}
599
600// advanceMessageWithID advances history to the specified message ID without
601// sending a message. This is useful e.g. for self-messages when echo-message
602// isn't enabled.
603func (dc *downstreamConn) advanceMessageWithID(msg *irc.Message, id string) {
[665]604 if id == "" || !dc.messageSupportsBacklog(msg) {
[428]605 return
606 }
607
608 dc.sendPing(id)
609}
610
611// ackMsgID acknowledges that a message has been received.
612func (dc *downstreamConn) ackMsgID(id string) {
[488]613 netID, entity, err := parseMsgID(id, nil)
[428]614 if err != nil {
615 dc.logger.Printf("failed to ACK message ID %q: %v", id, err)
616 return
617 }
618
[440]619 network := dc.user.getNetworkByID(netID)
[428]620 if network == nil {
621 return
622 }
623
[485]624 network.delivered.StoreID(entity, dc.clientName, id)
[428]625}
626
627func (dc *downstreamConn) sendPing(msgID string) {
[488]628 token := "soju-msgid-" + msgID
[428]629 dc.SendMessage(&irc.Message{
630 Command: "PING",
631 Params: []string{token},
632 })
633}
634
635func (dc *downstreamConn) handlePong(token string) {
636 if !strings.HasPrefix(token, "soju-msgid-") {
637 dc.logger.Printf("received unrecognized PONG token %q", token)
638 return
639 }
[488]640 msgID := strings.TrimPrefix(token, "soju-msgid-")
[428]641 dc.ackMsgID(msgID)
642}
643
[245]644// marshalMessage re-formats a message coming from an upstream connection so
645// that it's suitable for being sent on this downstream connection. Only
[665]646// messages that may appear in logs are supported, except MODE messages which
647// may only appear in single-upstream mode.
[261]648func (dc *downstreamConn) marshalMessage(msg *irc.Message, net *network) *irc.Message {
[686]649 msg = msg.Copy()
650 msg.Prefix = dc.marshalUserPrefix(net, msg.Prefix)
651
[665]652 if dc.network != nil {
653 return msg
654 }
655
[227]656 switch msg.Command {
[303]657 case "PRIVMSG", "NOTICE", "TAGMSG":
[261]658 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[245]659 case "NICK":
660 // Nick change for another user
[261]661 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[245]662 case "JOIN", "PART":
[261]663 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[245]664 case "KICK":
[261]665 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
666 msg.Params[1] = dc.marshalEntity(net, msg.Params[1])
[245]667 case "TOPIC":
[261]668 msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
[540]669 case "QUIT", "SETNAME":
[262]670 // This space is intentionally left blank
[227]671 default:
672 panic(fmt.Sprintf("unexpected %q message", msg.Command))
673 }
674
[245]675 return msg
[227]676}
677
[704]678func (dc *downstreamConn) handleMessage(ctx context.Context, msg *irc.Message) error {
679 ctx, cancel := dc.conn.NewContext(ctx)
[702]680 defer cancel()
681
[703]682 ctx, cancel = context.WithTimeout(ctx, handleDownstreamMessageTimeout)
683 defer cancel()
684
[13]685 switch msg.Command {
[28]686 case "QUIT":
[55]687 return dc.Close()
[13]688 default:
[55]689 if dc.registered {
[702]690 return dc.handleMessageRegistered(ctx, msg)
[13]691 } else {
[702]692 return dc.handleMessageUnregistered(ctx, msg)
[13]693 }
694 }
695}
696
[702]697func (dc *downstreamConn) handleMessageUnregistered(ctx context.Context, msg *irc.Message) error {
[13]698 switch msg.Command {
699 case "NICK":
[117]700 var nick string
701 if err := parseMessageParams(msg, &nick); err != nil {
[43]702 return err
[13]703 }
[717]704 if nick == "" || strings.ContainsAny(nick, illegalNickChars) {
[404]705 return ircError{&irc.Message{
706 Command: irc.ERR_ERRONEUSNICKNAME,
707 Params: []string{dc.nick, nick, "contains illegal characters"},
708 }}
709 }
[478]710 nickCM := casemapASCII(nick)
711 if nickCM == serviceNickCM {
[117]712 return ircError{&irc.Message{
713 Command: irc.ERR_NICKNAMEINUSE,
714 Params: []string{dc.nick, nick, "Nickname reserved for bouncer service"},
715 }}
716 }
717 dc.nick = nick
[478]718 dc.nickCM = nickCM
[13]719 case "USER":
[117]720 if err := parseMessageParams(msg, &dc.rawUsername, nil, nil, &dc.realname); err != nil {
[43]721 return err
[13]722 }
[85]723 case "PASS":
724 if err := parseMessageParams(msg, &dc.password); err != nil {
725 return err
726 }
[108]727 case "CAP":
728 var subCmd string
729 if err := parseMessageParams(msg, &subCmd); err != nil {
730 return err
731 }
732 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
733 return err
734 }
[112]735 case "AUTHENTICATE":
[724]736 credentials, err := dc.handleAuthenticateCommand(msg)
737 if err != nil {
738 return err
739 } else if credentials == nil {
740 break
[112]741 }
742
[724]743 if err := dc.authenticate(ctx, credentials.plainUsername, credentials.plainPassword); err != nil {
[726]744 dc.logger.Printf("SASL authentication error for user %q: %v", credentials.plainUsername, err)
[724]745 dc.endSASL(&irc.Message{
[716]746 Prefix: dc.srv.prefix(),
[125]747 Command: irc.ERR_SASLFAIL,
[726]748 Params: []string{dc.nick, authErrorReason(err)},
[112]749 })
[724]750 break
751 }
[112]752
[724]753 // Technically we should send RPL_LOGGEDIN here. However we use
754 // RPL_LOGGEDIN to mirror the upstream connection status. Let's
755 // see how many clients that breaks. See:
756 // https://github.com/ircv3/ircv3-specifications/pull/476
757 dc.endSASL(nil)
[532]758 case "BOUNCER":
759 var subcommand string
760 if err := parseMessageParams(msg, &subcommand); err != nil {
761 return err
762 }
763
764 switch strings.ToUpper(subcommand) {
765 case "BIND":
766 var idStr string
767 if err := parseMessageParams(msg, nil, &idStr); err != nil {
768 return err
769 }
770
771 if dc.user == nil {
772 return ircError{&irc.Message{
773 Command: "FAIL",
774 Params: []string{"BOUNCER", "ACCOUNT_REQUIRED", "BIND", "Authentication needed to bind to bouncer network"},
775 }}
776 }
777
[535]778 id, err := parseBouncerNetID(subcommand, idStr)
[532]779 if err != nil {
780 return err
781 }
782
783 var match *network
[768]784 for _, net := range dc.user.networks {
[532]785 if net.ID == id {
786 match = net
[768]787 break
[532]788 }
[768]789 }
[532]790 if match == nil {
791 return ircError{&irc.Message{
792 Command: "FAIL",
793 Params: []string{"BOUNCER", "INVALID_NETID", idStr, "Unknown network ID"},
794 }}
795 }
796
797 dc.networkName = match.GetName()
798 }
[13]799 default:
[55]800 dc.logger.Printf("unhandled message: %v", msg)
[13]801 return newUnknownCommandError(msg.Command)
802 }
[716]803 if dc.rawUsername != "" && dc.nick != "*" && !dc.negotiatingCaps {
[700]804 return dc.register(ctx)
[13]805 }
806 return nil
807}
808
[108]809func (dc *downstreamConn) handleCapCommand(cmd string, args []string) error {
[111]810 cmd = strings.ToUpper(cmd)
811
[108]812 switch cmd {
813 case "LS":
814 if len(args) > 0 {
815 var err error
816 if dc.capVersion, err = strconv.Atoi(args[0]); err != nil {
817 return err
818 }
819 }
[437]820 if !dc.registered && dc.capVersion >= 302 {
821 // Let downstream show everything it supports, and trim
822 // down the available capabilities when upstreams are
823 // known.
824 for k, v := range needAllDownstreamCaps {
825 dc.supportedCaps[k] = v
826 }
827 }
[108]828
[275]829 caps := make([]string, 0, len(dc.supportedCaps))
830 for k, v := range dc.supportedCaps {
831 if dc.capVersion >= 302 && v != "" {
[276]832 caps = append(caps, k+"="+v)
[275]833 } else {
834 caps = append(caps, k)
835 }
[112]836 }
[108]837
838 // TODO: multi-line replies
839 dc.SendMessage(&irc.Message{
840 Prefix: dc.srv.prefix(),
841 Command: "CAP",
[716]842 Params: []string{dc.nick, "LS", strings.Join(caps, " ")},
[108]843 })
844
[275]845 if dc.capVersion >= 302 {
846 // CAP version 302 implicitly enables cap-notify
847 dc.caps["cap-notify"] = true
848 }
849
[108]850 if !dc.registered {
[590]851 dc.negotiatingCaps = true
[108]852 }
853 case "LIST":
854 var caps []string
[521]855 for name, enabled := range dc.caps {
856 if enabled {
857 caps = append(caps, name)
858 }
[108]859 }
860
861 // TODO: multi-line replies
862 dc.SendMessage(&irc.Message{
863 Prefix: dc.srv.prefix(),
864 Command: "CAP",
[716]865 Params: []string{dc.nick, "LIST", strings.Join(caps, " ")},
[108]866 })
867 case "REQ":
868 if len(args) == 0 {
869 return ircError{&irc.Message{
870 Command: err_invalidcapcmd,
[716]871 Params: []string{dc.nick, cmd, "Missing argument in CAP REQ command"},
[108]872 }}
873 }
874
[275]875 // TODO: atomically ack/nak the whole capability set
[108]876 caps := strings.Fields(args[0])
877 ack := true
878 for _, name := range caps {
879 name = strings.ToLower(name)
880 enable := !strings.HasPrefix(name, "-")
881 if !enable {
882 name = strings.TrimPrefix(name, "-")
883 }
884
[275]885 if enable == dc.caps[name] {
[108]886 continue
887 }
888
[275]889 _, ok := dc.supportedCaps[name]
890 if !ok {
[108]891 ack = false
[275]892 break
[108]893 }
[275]894
895 if name == "cap-notify" && dc.capVersion >= 302 && !enable {
896 // cap-notify cannot be disabled with CAP version 302
897 ack = false
898 break
899 }
900
901 dc.caps[name] = enable
[108]902 }
903
904 reply := "NAK"
905 if ack {
906 reply = "ACK"
907 }
908 dc.SendMessage(&irc.Message{
909 Prefix: dc.srv.prefix(),
910 Command: "CAP",
[716]911 Params: []string{dc.nick, reply, args[0]},
[108]912 })
[590]913
914 if !dc.registered {
915 dc.negotiatingCaps = true
916 }
[108]917 case "END":
[590]918 dc.negotiatingCaps = false
[108]919 default:
920 return ircError{&irc.Message{
921 Command: err_invalidcapcmd,
[716]922 Params: []string{dc.nick, cmd, "Unknown CAP command"},
[108]923 }}
924 }
925 return nil
926}
927
[724]928func (dc *downstreamConn) handleAuthenticateCommand(msg *irc.Message) (result *downstreamSASL, err error) {
929 defer func() {
930 if err != nil {
931 dc.sasl = nil
932 }
933 }()
934
935 if !dc.caps["sasl"] {
936 return nil, ircError{&irc.Message{
937 Prefix: dc.srv.prefix(),
938 Command: irc.ERR_SASLFAIL,
[754]939 Params: []string{dc.nick, "AUTHENTICATE requires the \"sasl\" capability to be enabled"},
[724]940 }}
941 }
942 if len(msg.Params) == 0 {
943 return nil, ircError{&irc.Message{
944 Prefix: dc.srv.prefix(),
945 Command: irc.ERR_SASLFAIL,
[754]946 Params: []string{dc.nick, "Missing AUTHENTICATE argument"},
[724]947 }}
948 }
949 if msg.Params[0] == "*" {
950 return nil, ircError{&irc.Message{
951 Prefix: dc.srv.prefix(),
952 Command: irc.ERR_SASLABORTED,
[754]953 Params: []string{dc.nick, "SASL authentication aborted"},
[724]954 }}
955 }
956
957 var resp []byte
958 if dc.sasl == nil {
959 mech := strings.ToUpper(msg.Params[0])
960 var server sasl.Server
961 switch mech {
962 case "PLAIN":
963 server = sasl.NewPlainServer(sasl.PlainAuthenticator(func(identity, username, password string) error {
964 dc.sasl.plainUsername = username
965 dc.sasl.plainPassword = password
966 return nil
967 }))
968 default:
969 return nil, ircError{&irc.Message{
970 Prefix: dc.srv.prefix(),
971 Command: irc.ERR_SASLFAIL,
[754]972 Params: []string{dc.nick, fmt.Sprintf("Unsupported SASL mechanism %q", mech)},
[724]973 }}
974 }
975
976 dc.sasl = &downstreamSASL{server: server}
977 } else {
[760]978 chunk := msg.Params[0]
979 if chunk == "+" {
980 chunk = ""
981 }
982
983 if dc.sasl.pendingResp.Len()+len(chunk) > 10*1024 {
[724]984 return nil, ircError{&irc.Message{
985 Prefix: dc.srv.prefix(),
986 Command: irc.ERR_SASLFAIL,
[760]987 Params: []string{dc.nick, "Response too long"},
988 }}
989 }
990
991 dc.sasl.pendingResp.WriteString(chunk)
992
[761]993 if len(chunk) == maxSASLLength {
[760]994 return nil, nil // Multi-line response, wait for the next command
995 }
996
997 resp, err = base64.StdEncoding.DecodeString(dc.sasl.pendingResp.String())
998 if err != nil {
999 return nil, ircError{&irc.Message{
1000 Prefix: dc.srv.prefix(),
1001 Command: irc.ERR_SASLFAIL,
[754]1002 Params: []string{dc.nick, "Invalid base64-encoded response"},
[724]1003 }}
1004 }
[760]1005
1006 dc.sasl.pendingResp.Reset()
[724]1007 }
1008
1009 challenge, done, err := dc.sasl.server.Next(resp)
1010 if err != nil {
1011 return nil, err
1012 } else if done {
1013 return dc.sasl, nil
1014 } else {
1015 challengeStr := "+"
1016 if len(challenge) > 0 {
1017 challengeStr = base64.StdEncoding.EncodeToString(challenge)
1018 }
1019
1020 // TODO: multi-line messages
1021 dc.SendMessage(&irc.Message{
1022 Prefix: dc.srv.prefix(),
1023 Command: "AUTHENTICATE",
1024 Params: []string{challengeStr},
1025 })
1026 return nil, nil
1027 }
1028}
1029
1030func (dc *downstreamConn) endSASL(msg *irc.Message) {
1031 if dc.sasl == nil {
1032 return
1033 }
1034
1035 dc.sasl = nil
1036
1037 if msg != nil {
1038 dc.SendMessage(msg)
1039 } else {
1040 dc.SendMessage(&irc.Message{
1041 Prefix: dc.srv.prefix(),
1042 Command: irc.RPL_SASLSUCCESS,
1043 Params: []string{dc.nick, "SASL authentication successful"},
1044 })
1045 }
1046}
1047
[275]1048func (dc *downstreamConn) setSupportedCap(name, value string) {
1049 prevValue, hasPrev := dc.supportedCaps[name]
1050 changed := !hasPrev || prevValue != value
1051 dc.supportedCaps[name] = value
1052
1053 if !dc.caps["cap-notify"] || !changed {
1054 return
1055 }
1056
1057 cap := name
1058 if value != "" && dc.capVersion >= 302 {
1059 cap = name + "=" + value
1060 }
1061
1062 dc.SendMessage(&irc.Message{
1063 Prefix: dc.srv.prefix(),
1064 Command: "CAP",
[716]1065 Params: []string{dc.nick, "NEW", cap},
[275]1066 })
1067}
1068
1069func (dc *downstreamConn) unsetSupportedCap(name string) {
1070 _, hasPrev := dc.supportedCaps[name]
1071 delete(dc.supportedCaps, name)
1072 delete(dc.caps, name)
1073
1074 if !dc.caps["cap-notify"] || !hasPrev {
1075 return
1076 }
1077
1078 dc.SendMessage(&irc.Message{
1079 Prefix: dc.srv.prefix(),
1080 Command: "CAP",
[716]1081 Params: []string{dc.nick, "DEL", name},
[275]1082 })
1083}
1084
[276]1085func (dc *downstreamConn) updateSupportedCaps() {
[292]1086 supportedCaps := make(map[string]bool)
1087 for cap := range needAllDownstreamCaps {
1088 supportedCaps[cap] = true
1089 }
[276]1090 dc.forEachUpstream(func(uc *upstreamConn) {
[292]1091 for cap, supported := range supportedCaps {
1092 supportedCaps[cap] = supported && uc.caps[cap]
1093 }
[276]1094 })
1095
[292]1096 for cap, supported := range supportedCaps {
1097 if supported {
1098 dc.setSupportedCap(cap, needAllDownstreamCaps[cap])
1099 } else {
1100 dc.unsetSupportedCap(cap)
1101 }
[276]1102 }
[665]1103
[725]1104 if uc := dc.upstream(); uc != nil && uc.supportsSASL("PLAIN") {
1105 dc.setSupportedCap("sasl", "PLAIN")
1106 } else if dc.network != nil {
1107 dc.unsetSupportedCap("sasl")
1108 }
1109
[729]1110 if uc := dc.upstream(); uc != nil && uc.caps["draft/account-registration"] {
1111 // Strip "before-connect", because we require downstreams to be fully
1112 // connected before attempting account registration.
1113 values := strings.Split(uc.supportedCaps["draft/account-registration"], ",")
1114 for i, v := range values {
1115 if v == "before-connect" {
1116 values = append(values[:i], values[i+1:]...)
1117 break
1118 }
1119 }
1120 dc.setSupportedCap("draft/account-registration", strings.Join(values, ","))
1121 } else {
1122 dc.unsetSupportedCap("draft/account-registration")
1123 }
1124
[691]1125 if _, ok := dc.user.msgStore.(chatHistoryMessageStore); ok && dc.network != nil {
[665]1126 dc.setSupportedCap("draft/event-playback", "")
1127 } else {
1128 dc.unsetSupportedCap("draft/event-playback")
1129 }
[276]1130}
1131
[296]1132func (dc *downstreamConn) updateNick() {
1133 if uc := dc.upstream(); uc != nil && uc.nick != dc.nick {
1134 dc.SendMessage(&irc.Message{
1135 Prefix: dc.prefix(),
1136 Command: "NICK",
1137 Params: []string{uc.nick},
1138 })
1139 dc.nick = uc.nick
[478]1140 dc.nickCM = casemapASCII(dc.nick)
[296]1141 }
1142}
1143
[540]1144func (dc *downstreamConn) updateRealname() {
1145 if uc := dc.upstream(); uc != nil && uc.realname != dc.realname && dc.caps["setname"] {
1146 dc.SendMessage(&irc.Message{
1147 Prefix: dc.prefix(),
1148 Command: "SETNAME",
1149 Params: []string{uc.realname},
1150 })
1151 dc.realname = uc.realname
1152 }
1153}
1154
[722]1155func (dc *downstreamConn) updateAccount() {
[723]1156 var account string
1157 if dc.network == nil {
1158 account = dc.user.Username
1159 } else if uc := dc.upstream(); uc != nil {
1160 account = uc.account
1161 } else {
[722]1162 return
1163 }
1164
[723]1165 if dc.account == account || !dc.caps["sasl"] {
1166 return
1167 }
1168
1169 if account != "" {
[722]1170 dc.SendMessage(&irc.Message{
1171 Prefix: dc.srv.prefix(),
1172 Command: irc.RPL_LOGGEDIN,
[723]1173 Params: []string{dc.nick, dc.prefix().String(), account, "You are logged in as " + account},
[722]1174 })
1175 } else {
1176 dc.SendMessage(&irc.Message{
1177 Prefix: dc.srv.prefix(),
1178 Command: irc.RPL_LOGGEDOUT,
1179 Params: []string{dc.nick, dc.prefix().String(), "You are logged out"},
1180 })
1181 }
1182
[723]1183 dc.account = account
[722]1184}
1185
[698]1186func sanityCheckServer(ctx context.Context, addr string) error {
[699]1187 ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
[698]1188 defer cancel()
1189
1190 conn, err := new(tls.Dialer).DialContext(ctx, "tcp", addr)
[91]1191 if err != nil {
1192 return err
1193 }
[698]1194
[91]1195 return conn.Close()
1196}
1197
[183]1198func unmarshalUsername(rawUsername string) (username, client, network string) {
[112]1199 username = rawUsername
[183]1200
1201 i := strings.IndexAny(username, "/@")
1202 j := strings.LastIndexAny(username, "/@")
1203 if i >= 0 {
1204 username = rawUsername[:i]
[73]1205 }
[183]1206 if j >= 0 {
[190]1207 if rawUsername[j] == '@' {
1208 client = rawUsername[j+1:]
1209 } else {
1210 network = rawUsername[j+1:]
1211 }
[73]1212 }
[183]1213 if i >= 0 && j >= 0 && i < j {
[190]1214 if rawUsername[i] == '@' {
1215 client = rawUsername[i+1 : j]
1216 } else {
1217 network = rawUsername[i+1 : j]
1218 }
[183]1219 }
1220
1221 return username, client, network
[112]1222}
[73]1223
[700]1224func (dc *downstreamConn) authenticate(ctx context.Context, username, password string) error {
[183]1225 username, clientName, networkName := unmarshalUsername(username)
[168]1226
[700]1227 u, err := dc.srv.db.GetUser(ctx, username)
[173]1228 if err != nil {
[726]1229 return newInvalidUsernameOrPasswordError(fmt.Errorf("user not found: %w", err))
[168]1230 }
1231
[322]1232 // Password auth disabled
1233 if u.Password == "" {
[726]1234 return newInvalidUsernameOrPasswordError(fmt.Errorf("password auth disabled"))
[322]1235 }
1236
[173]1237 err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
[168]1238 if err != nil {
[726]1239 return newInvalidUsernameOrPasswordError(fmt.Errorf("wrong password"))
[168]1240 }
1241
[173]1242 dc.user = dc.srv.getUser(username)
1243 if dc.user == nil {
[726]1244 return fmt.Errorf("user not active")
[173]1245 }
[183]1246 dc.clientName = clientName
[168]1247 dc.networkName = networkName
1248 return nil
1249}
1250
[700]1251func (dc *downstreamConn) register(ctx context.Context) error {
[168]1252 if dc.registered {
[788]1253 panic("tried to register twice")
[168]1254 }
1255
[724]1256 if dc.sasl != nil {
1257 dc.endSASL(&irc.Message{
[721]1258 Prefix: dc.srv.prefix(),
1259 Command: irc.ERR_SASLABORTED,
[754]1260 Params: []string{dc.nick, "SASL authentication aborted"},
[721]1261 })
1262 }
1263
[168]1264 password := dc.password
1265 dc.password = ""
1266 if dc.user == nil {
[753]1267 if password == "" {
1268 if dc.caps["sasl"] {
1269 return ircError{&irc.Message{
1270 Command: "FAIL",
1271 Params: []string{"*", "ACCOUNT_REQUIRED", "Authentication required"},
1272 }}
1273 } else {
1274 return ircError{&irc.Message{
1275 Command: irc.ERR_PASSWDMISMATCH,
1276 Params: []string{dc.nick, "Authentication required"},
1277 }}
1278 }
1279 }
1280
[700]1281 if err := dc.authenticate(ctx, dc.rawUsername, password); err != nil {
[726]1282 dc.logger.Printf("PASS authentication error for user %q: %v", dc.rawUsername, err)
1283 return ircError{&irc.Message{
1284 Command: irc.ERR_PASSWDMISMATCH,
[753]1285 Params: []string{dc.nick, authErrorReason(err)},
[726]1286 }}
[168]1287 }
1288 }
1289
[183]1290 if dc.clientName == "" && dc.networkName == "" {
1291 _, dc.clientName, dc.networkName = unmarshalUsername(dc.rawUsername)
[168]1292 }
1293
1294 dc.registered = true
[184]1295 dc.logger.Printf("registration complete for user %q", dc.user.Username)
[168]1296 return nil
1297}
1298
[701]1299func (dc *downstreamConn) loadNetwork(ctx context.Context) error {
[168]1300 if dc.networkName == "" {
[112]1301 return nil
1302 }
[85]1303
[168]1304 network := dc.user.getNetwork(dc.networkName)
[112]1305 if network == nil {
[168]1306 addr := dc.networkName
[112]1307 if !strings.ContainsRune(addr, ':') {
1308 addr = addr + ":6697"
1309 }
1310
1311 dc.logger.Printf("trying to connect to new network %q", addr)
[701]1312 if err := sanityCheckServer(ctx, addr); err != nil {
[112]1313 dc.logger.Printf("failed to connect to %q: %v", addr, err)
1314 return ircError{&irc.Message{
1315 Command: irc.ERR_PASSWDMISMATCH,
[754]1316 Params: []string{dc.nick, fmt.Sprintf("Failed to connect to %q", dc.networkName)},
[112]1317 }}
1318 }
1319
[354]1320 // Some clients only allow specifying the nickname (and use the
1321 // nickname as a username too). Strip the network name from the
1322 // nickname when auto-saving networks.
1323 nick, _, _ := unmarshalUsername(dc.nick)
1324
[168]1325 dc.logger.Printf("auto-saving network %q", dc.networkName)
[112]1326 var err error
[701]1327 network, err = dc.user.createNetwork(ctx, &Network{
[542]1328 Addr: dc.networkName,
1329 Nick: nick,
1330 Enabled: true,
[120]1331 })
[112]1332 if err != nil {
1333 return err
1334 }
1335 }
1336
1337 dc.network = network
1338 return nil
1339}
1340
[701]1341func (dc *downstreamConn) welcome(ctx context.Context) error {
[168]1342 if dc.user == nil || !dc.registered {
1343 panic("tried to welcome an unregistered connection")
[37]1344 }
1345
[750]1346 remoteAddr := dc.conn.RemoteAddr().String()
1347 dc.logger = &prefixLogger{dc.srv.Logger, fmt.Sprintf("user %q: downstream %q: ", dc.user.Username, remoteAddr)}
1348
[168]1349 // TODO: doing this might take some time. We should do it in dc.register
1350 // instead, but we'll potentially be adding a new network and this must be
1351 // done in the user goroutine.
[701]1352 if err := dc.loadNetwork(ctx); err != nil {
[168]1353 return err
[85]1354 }
1355
[694]1356 if dc.network == nil && !dc.caps["soju.im/bouncer-networks"] && dc.srv.Config().MultiUpstream {
[693]1357 dc.isMultiUpstream = true
1358 }
1359
[706]1360 dc.updateSupportedCaps()
1361
[446]1362 isupport := []string{
[670]1363 fmt.Sprintf("CHATHISTORY=%v", chatHistoryLimit),
[478]1364 "CASEMAPPING=ascii",
[446]1365 }
1366
[532]1367 if dc.network != nil {
1368 isupport = append(isupport, fmt.Sprintf("BOUNCER_NETID=%v", dc.network.ID))
1369 }
[691]1370 if title := dc.srv.Config().Title; dc.network == nil && title != "" {
1371 isupport = append(isupport, "NETWORK="+encodeISUPPORT(title))
[662]1372 }
[693]1373 if dc.network == nil && !dc.isMultiUpstream {
[660]1374 isupport = append(isupport, "WHOX")
1375 }
1376
[463]1377 if uc := dc.upstream(); uc != nil {
1378 for k := range passthroughIsupport {
1379 v, ok := uc.isupport[k]
1380 if !ok {
1381 continue
1382 }
1383 if v != nil {
1384 isupport = append(isupport, fmt.Sprintf("%v=%v", k, *v))
1385 } else {
1386 isupport = append(isupport, k)
1387 }
1388 }
[447]1389 }
1390
[55]1391 dc.SendMessage(&irc.Message{
1392 Prefix: dc.srv.prefix(),
[13]1393 Command: irc.RPL_WELCOME,
[98]1394 Params: []string{dc.nick, "Welcome to soju, " + dc.nick},
[54]1395 })
[55]1396 dc.SendMessage(&irc.Message{
1397 Prefix: dc.srv.prefix(),
[13]1398 Command: irc.RPL_YOURHOST,
[691]1399 Params: []string{dc.nick, "Your host is " + dc.srv.Config().Hostname},
[54]1400 })
[55]1401 dc.SendMessage(&irc.Message{
1402 Prefix: dc.srv.prefix(),
[13]1403 Command: irc.RPL_MYINFO,
[691]1404 Params: []string{dc.nick, dc.srv.Config().Hostname, "soju", "aiwroO", "OovaimnqpsrtklbeI"},
[54]1405 })
[463]1406 for _, msg := range generateIsupport(dc.srv.prefix(), dc.nick, isupport) {
1407 dc.SendMessage(msg)
1408 }
[553]1409 if uc := dc.upstream(); uc != nil {
1410 dc.SendMessage(&irc.Message{
1411 Prefix: dc.srv.prefix(),
1412 Command: irc.RPL_UMODEIS,
[672]1413 Params: []string{dc.nick, "+" + string(uc.modes)},
[553]1414 })
1415 }
[693]1416 if dc.network == nil && !dc.isMultiUpstream && dc.user.Admin {
[671]1417 dc.SendMessage(&irc.Message{
1418 Prefix: dc.srv.prefix(),
1419 Command: irc.RPL_UMODEIS,
1420 Params: []string{dc.nick, "+o"},
1421 })
1422 }
[13]1423
[706]1424 dc.updateNick()
1425 dc.updateRealname()
[722]1426 dc.updateAccount()
[706]1427
[691]1428 if motd := dc.user.srv.Config().MOTD; motd != "" && dc.network == nil {
[636]1429 for _, msg := range generateMOTD(dc.srv.prefix(), dc.nick, motd) {
1430 dc.SendMessage(msg)
1431 }
1432 } else {
1433 motdHint := "No MOTD"
1434 if dc.network != nil {
1435 motdHint = "Use /motd to read the message of the day"
1436 }
1437 dc.SendMessage(&irc.Message{
1438 Prefix: dc.srv.prefix(),
1439 Command: irc.ERR_NOMOTD,
1440 Params: []string{dc.nick, motdHint},
1441 })
1442 }
1443
[535]1444 if dc.caps["soju.im/bouncer-networks-notify"] {
[551]1445 dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
[768]1446 for _, network := range dc.user.networks {
[551]1447 idStr := fmt.Sprintf("%v", network.ID)
1448 attrs := getNetworkAttrs(network)
1449 dc.SendMessage(&irc.Message{
1450 Tags: irc.Tags{"batch": batchRef},
1451 Prefix: dc.srv.prefix(),
1452 Command: "BOUNCER",
1453 Params: []string{"NETWORK", idStr, attrs.String()},
1454 })
[768]1455 }
[535]1456 })
1457 }
1458
[73]1459 dc.forEachUpstream(func(uc *upstreamConn) {
[478]1460 for _, entry := range uc.channels.innerMap {
1461 ch := entry.value.(*upstreamChannel)
[284]1462 if !ch.complete {
1463 continue
1464 }
[478]1465 record := uc.network.channels.Value(ch.Name)
1466 if record != nil && record.Detached {
[284]1467 continue
1468 }
[132]1469
[284]1470 dc.SendMessage(&irc.Message{
1471 Prefix: dc.prefix(),
1472 Command: "JOIN",
1473 Params: []string{dc.marshalEntity(ch.conn.network, ch.Name)},
1474 })
1475
[781]1476 forwardChannel(ctx, dc, ch)
[30]1477 }
[143]1478 })
[50]1479
[143]1480 dc.forEachNetwork(func(net *network) {
[496]1481 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
1482 return
1483 }
1484
[253]1485 // Only send history if we're the first connected client with that name
1486 // for the network
[482]1487 firstClient := true
1488 dc.user.forEachDownstream(func(c *downstreamConn) {
1489 if c != dc && c.clientName == dc.clientName && c.network == dc.network {
1490 firstClient = false
1491 }
1492 })
1493 if firstClient {
[485]1494 net.delivered.ForEachTarget(func(target string) {
[495]1495 lastDelivered := net.delivered.LoadID(target, dc.clientName)
1496 if lastDelivered == "" {
1497 return
1498 }
1499
[701]1500 dc.sendTargetBacklog(ctx, net, target, lastDelivered)
[495]1501
1502 // Fast-forward history to last message
1503 targetCM := net.casemap(target)
[666]1504 lastID, err := dc.user.msgStore.LastMsgID(&net.Network, targetCM, time.Now())
[495]1505 if err != nil {
1506 dc.logger.Printf("failed to get last message ID: %v", err)
1507 return
1508 }
1509 net.delivered.StoreID(target, dc.clientName, lastID)
[485]1510 })
[227]1511 }
[253]1512 })
[57]1513
[253]1514 return nil
1515}
[144]1516
[665]1517// messageSupportsBacklog checks whether the provided message can be sent as
[428]1518// part of an history batch.
[665]1519func (dc *downstreamConn) messageSupportsBacklog(msg *irc.Message) bool {
[428]1520 // Don't replay all messages, because that would mess up client
1521 // state. For instance we just sent the list of users, sending
1522 // PART messages for one of these users would be incorrect.
1523 switch msg.Command {
1524 case "PRIVMSG", "NOTICE":
1525 return true
1526 }
1527 return false
1528}
1529
[701]1530func (dc *downstreamConn) sendTargetBacklog(ctx context.Context, net *network, target, msgID string) {
[423]1531 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
[319]1532 return
1533 }
[485]1534
[499]1535 ch := net.channels.Value(target)
1536
[701]1537 ctx, cancel := context.WithTimeout(ctx, backlogTimeout)
[667]1538 defer cancel()
1539
[484]1540 targetCM := net.casemap(target)
[670]1541 history, err := dc.user.msgStore.LoadLatestID(ctx, &net.Network, targetCM, msgID, backlogLimit)
[452]1542 if err != nil {
[495]1543 dc.logger.Printf("failed to send backlog for %q: %v", target, err)
[452]1544 return
1545 }
[253]1546
[551]1547 dc.SendBatch("chathistory", []string{dc.marshalEntity(net, target)}, nil, func(batchRef irc.TagValue) {
1548 for _, msg := range history {
1549 if ch != nil && ch.Detached {
1550 if net.detachedMessageNeedsRelay(ch, msg) {
1551 dc.relayDetachedMessage(net, msg)
1552 }
1553 } else {
[651]1554 msg.Tags["batch"] = batchRef
[551]1555 dc.SendMessage(dc.marshalMessage(msg, net))
[499]1556 }
[256]1557 }
[551]1558 })
[13]1559}
1560
[499]1561func (dc *downstreamConn) relayDetachedMessage(net *network, msg *irc.Message) {
1562 if msg.Command != "PRIVMSG" && msg.Command != "NOTICE" {
1563 return
1564 }
1565
1566 sender := msg.Prefix.Name
1567 target, text := msg.Params[0], msg.Params[1]
1568 if net.isHighlight(msg) {
1569 sendServiceNOTICE(dc, fmt.Sprintf("highlight in %v: <%v> %v", dc.marshalEntity(net, target), sender, text))
1570 } else {
1571 sendServiceNOTICE(dc, fmt.Sprintf("message in %v: <%v> %v", dc.marshalEntity(net, target), sender, text))
1572 }
1573}
1574
[103]1575func (dc *downstreamConn) runUntilRegistered() error {
[704]1576 ctx, cancel := context.WithTimeout(context.TODO(), downstreamRegisterTimeout)
1577 defer cancel()
1578
1579 // Close the connection with an error if the deadline is exceeded
1580 go func() {
1581 <-ctx.Done()
1582 if err := ctx.Err(); err == context.DeadlineExceeded {
1583 dc.SendMessage(&irc.Message{
1584 Prefix: dc.srv.prefix(),
1585 Command: "ERROR",
1586 Params: []string{"Connection registration timed out"},
1587 })
1588 dc.Close()
1589 }
1590 }()
1591
[103]1592 for !dc.registered {
[212]1593 msg, err := dc.ReadMessage()
[106]1594 if err != nil {
[655]1595 return fmt.Errorf("failed to read IRC command: %w", err)
[103]1596 }
1597
[704]1598 err = dc.handleMessage(ctx, msg)
[103]1599 if ircErr, ok := err.(ircError); ok {
1600 ircErr.Message.Prefix = dc.srv.prefix()
1601 dc.SendMessage(ircErr.Message)
1602 } else if err != nil {
1603 return fmt.Errorf("failed to handle IRC command %q: %v", msg, err)
1604 }
1605 }
1606
1607 return nil
1608}
1609
[702]1610func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.Message) error {
[13]1611 switch msg.Command {
[111]1612 case "CAP":
1613 var subCmd string
1614 if err := parseMessageParams(msg, &subCmd); err != nil {
1615 return err
1616 }
1617 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
1618 return err
1619 }
[107]1620 case "PING":
[412]1621 var source, destination string
1622 if err := parseMessageParams(msg, &source); err != nil {
1623 return err
1624 }
1625 if len(msg.Params) > 1 {
1626 destination = msg.Params[1]
1627 }
[691]1628 hostname := dc.srv.Config().Hostname
1629 if destination != "" && destination != hostname {
[412]1630 return ircError{&irc.Message{
1631 Command: irc.ERR_NOSUCHSERVER,
[413]1632 Params: []string{dc.nick, destination, "No such server"},
[412]1633 }}
1634 }
[107]1635 dc.SendMessage(&irc.Message{
1636 Prefix: dc.srv.prefix(),
1637 Command: "PONG",
[691]1638 Params: []string{hostname, source},
[107]1639 })
1640 return nil
[428]1641 case "PONG":
1642 if len(msg.Params) == 0 {
1643 return newNeedMoreParamsError(msg.Command)
1644 }
1645 token := msg.Params[len(msg.Params)-1]
1646 dc.handlePong(token)
[42]1647 case "USER":
[13]1648 return ircError{&irc.Message{
1649 Command: irc.ERR_ALREADYREGISTERED,
[55]1650 Params: []string{dc.nick, "You may not reregister"},
[13]1651 }}
[42]1652 case "NICK":
[429]1653 var rawNick string
1654 if err := parseMessageParams(msg, &rawNick); err != nil {
[90]1655 return err
1656 }
1657
[429]1658 nick := rawNick
[297]1659 var upstream *upstreamConn
1660 if dc.upstream() == nil {
1661 uc, unmarshaledNick, err := dc.unmarshalEntity(nick)
1662 if err == nil { // NICK nick/network: NICK only on a specific upstream
1663 upstream = uc
1664 nick = unmarshaledNick
1665 }
1666 }
1667
[717]1668 if nick == "" || strings.ContainsAny(nick, illegalNickChars) {
[404]1669 return ircError{&irc.Message{
1670 Command: irc.ERR_ERRONEUSNICKNAME,
[430]1671 Params: []string{dc.nick, rawNick, "contains illegal characters"},
[404]1672 }}
1673 }
[478]1674 if casemapASCII(nick) == serviceNickCM {
[429]1675 return ircError{&irc.Message{
1676 Command: irc.ERR_NICKNAMEINUSE,
1677 Params: []string{dc.nick, rawNick, "Nickname reserved for bouncer service"},
1678 }}
1679 }
[404]1680
[90]1681 var err error
1682 dc.forEachNetwork(func(n *network) {
[297]1683 if err != nil || (upstream != nil && upstream.network != n) {
[90]1684 return
1685 }
1686 n.Nick = nick
[675]1687 err = dc.srv.db.StoreNetwork(ctx, dc.user.ID, &n.Network)
[90]1688 })
1689 if err != nil {
1690 return err
1691 }
1692
[73]1693 dc.forEachUpstream(func(uc *upstreamConn) {
[297]1694 if upstream != nil && upstream != uc {
1695 return
1696 }
[757]1697 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[297]1698 Command: "NICK",
1699 Params: []string{nick},
1700 })
[42]1701 })
[296]1702
[512]1703 if dc.upstream() == nil && upstream == nil && dc.nick != nick {
[296]1704 dc.SendMessage(&irc.Message{
1705 Prefix: dc.prefix(),
1706 Command: "NICK",
1707 Params: []string{nick},
1708 })
1709 dc.nick = nick
[478]1710 dc.nickCM = casemapASCII(dc.nick)
[296]1711 }
[540]1712 case "SETNAME":
1713 var realname string
1714 if err := parseMessageParams(msg, &realname); err != nil {
1715 return err
1716 }
1717
[568]1718 // If the client just resets to the default, just wipe the per-network
1719 // preference
1720 storeRealname := realname
1721 if realname == dc.user.Realname {
1722 storeRealname = ""
1723 }
1724
[540]1725 var storeErr error
1726 var needUpdate []Network
1727 dc.forEachNetwork(func(n *network) {
1728 // We only need to call updateNetwork for upstreams that don't
1729 // support setname
1730 if uc := n.conn; uc != nil && uc.caps["setname"] {
[757]1731 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[540]1732 Command: "SETNAME",
1733 Params: []string{realname},
1734 })
1735
[568]1736 n.Realname = storeRealname
[675]1737 if err := dc.srv.db.StoreNetwork(ctx, dc.user.ID, &n.Network); err != nil {
[540]1738 dc.logger.Printf("failed to store network realname: %v", err)
1739 storeErr = err
1740 }
1741 return
1742 }
1743
1744 record := n.Network // copy network record because we'll mutate it
[568]1745 record.Realname = storeRealname
[540]1746 needUpdate = append(needUpdate, record)
1747 })
1748
1749 // Walk the network list as a second step, because updateNetwork
1750 // mutates the original list
1751 for _, record := range needUpdate {
[676]1752 if _, err := dc.user.updateNetwork(ctx, &record); err != nil {
[540]1753 dc.logger.Printf("failed to update network realname: %v", err)
1754 storeErr = err
1755 }
1756 }
1757 if storeErr != nil {
1758 return ircError{&irc.Message{
1759 Command: "FAIL",
1760 Params: []string{"SETNAME", "CANNOT_CHANGE_REALNAME", "Failed to update realname"},
1761 }}
1762 }
1763
[651]1764 if dc.upstream() == nil {
[540]1765 dc.SendMessage(&irc.Message{
1766 Prefix: dc.prefix(),
1767 Command: "SETNAME",
1768 Params: []string{realname},
1769 })
1770 }
[146]1771 case "JOIN":
1772 var namesStr string
1773 if err := parseMessageParams(msg, &namesStr); err != nil {
[48]1774 return err
1775 }
1776
[146]1777 var keys []string
1778 if len(msg.Params) > 1 {
1779 keys = strings.Split(msg.Params[1], ",")
1780 }
1781
1782 for i, name := range strings.Split(namesStr, ",") {
[145]1783 uc, upstreamName, err := dc.unmarshalEntity(name)
1784 if err != nil {
[158]1785 return err
[145]1786 }
[48]1787
[146]1788 var key string
1789 if len(keys) > i {
1790 key = keys[i]
1791 }
1792
[545]1793 if !uc.isChannel(upstreamName) {
1794 dc.SendMessage(&irc.Message{
1795 Prefix: dc.srv.prefix(),
1796 Command: irc.ERR_NOSUCHCHANNEL,
1797 Params: []string{name, "Not a channel name"},
1798 })
1799 continue
1800 }
1801
[758]1802 // Most servers ignore duplicate JOIN messages. We ignore them here
1803 // because some clients automatically send JOIN messages in bulk
1804 // when reconnecting to the bouncer. We don't want to flood the
1805 // upstream connection with these.
1806 if !uc.channels.Has(upstreamName) {
1807 params := []string{upstreamName}
1808 if key != "" {
1809 params = append(params, key)
1810 }
1811 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
1812 Command: "JOIN",
1813 Params: params,
1814 })
[146]1815 }
[89]1816
[478]1817 ch := uc.network.channels.Value(upstreamName)
1818 if ch != nil {
[285]1819 // Don't clear the channel key if there's one set
1820 // TODO: add a way to unset the channel key
[435]1821 if key != "" {
1822 ch.Key = key
1823 }
[781]1824 uc.network.attach(ctx, ch)
[435]1825 } else {
1826 ch = &Channel{
1827 Name: upstreamName,
1828 Key: key,
1829 }
[478]1830 uc.network.channels.SetValue(upstreamName, ch)
[285]1831 }
[675]1832 if err := dc.srv.db.StoreChannel(ctx, uc.network.ID, ch); err != nil {
[222]1833 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
[89]1834 }
1835 }
[146]1836 case "PART":
1837 var namesStr string
1838 if err := parseMessageParams(msg, &namesStr); err != nil {
1839 return err
1840 }
1841
1842 var reason string
1843 if len(msg.Params) > 1 {
1844 reason = msg.Params[1]
1845 }
1846
1847 for _, name := range strings.Split(namesStr, ",") {
1848 uc, upstreamName, err := dc.unmarshalEntity(name)
1849 if err != nil {
[158]1850 return err
[146]1851 }
1852
[284]1853 if strings.EqualFold(reason, "detach") {
[478]1854 ch := uc.network.channels.Value(upstreamName)
1855 if ch != nil {
[435]1856 uc.network.detach(ch)
1857 } else {
1858 ch = &Channel{
1859 Name: name,
1860 Detached: true,
1861 }
[478]1862 uc.network.channels.SetValue(upstreamName, ch)
[284]1863 }
[675]1864 if err := dc.srv.db.StoreChannel(ctx, uc.network.ID, ch); err != nil {
[435]1865 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1866 }
[284]1867 } else {
1868 params := []string{upstreamName}
1869 if reason != "" {
1870 params = append(params, reason)
1871 }
[757]1872 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[284]1873 Command: "PART",
1874 Params: params,
1875 })
[146]1876
[676]1877 if err := uc.network.deleteChannel(ctx, upstreamName); err != nil {
[284]1878 dc.logger.Printf("failed to delete channel %q: %v", upstreamName, err)
1879 }
[146]1880 }
1881 }
[159]1882 case "KICK":
1883 var channelStr, userStr string
1884 if err := parseMessageParams(msg, &channelStr, &userStr); err != nil {
1885 return err
1886 }
1887
1888 channels := strings.Split(channelStr, ",")
1889 users := strings.Split(userStr, ",")
1890
1891 var reason string
1892 if len(msg.Params) > 2 {
1893 reason = msg.Params[2]
1894 }
1895
1896 if len(channels) != 1 && len(channels) != len(users) {
1897 return ircError{&irc.Message{
1898 Command: irc.ERR_BADCHANMASK,
1899 Params: []string{dc.nick, channelStr, "Bad channel mask"},
1900 }}
1901 }
1902
1903 for i, user := range users {
1904 var channel string
1905 if len(channels) == 1 {
1906 channel = channels[0]
1907 } else {
1908 channel = channels[i]
1909 }
1910
1911 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1912 if err != nil {
1913 return err
1914 }
1915
1916 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1917 if err != nil {
1918 return err
1919 }
1920
1921 if ucChannel != ucUser {
1922 return ircError{&irc.Message{
1923 Command: irc.ERR_USERNOTINCHANNEL,
[400]1924 Params: []string{dc.nick, user, channel, "They are on another network"},
[159]1925 }}
1926 }
1927 uc := ucChannel
1928
1929 params := []string{upstreamChannel, upstreamUser}
1930 if reason != "" {
1931 params = append(params, reason)
1932 }
[757]1933 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[159]1934 Command: "KICK",
1935 Params: params,
1936 })
1937 }
[69]1938 case "MODE":
[46]1939 var name string
1940 if err := parseMessageParams(msg, &name); err != nil {
1941 return err
1942 }
1943
1944 var modeStr string
1945 if len(msg.Params) > 1 {
1946 modeStr = msg.Params[1]
1947 }
1948
[478]1949 if casemapASCII(name) == dc.nickCM {
[46]1950 if modeStr != "" {
[554]1951 if uc := dc.upstream(); uc != nil {
[757]1952 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[69]1953 Command: "MODE",
1954 Params: []string{uc.nick, modeStr},
1955 })
[554]1956 } else {
1957 dc.SendMessage(&irc.Message{
1958 Prefix: dc.srv.prefix(),
1959 Command: irc.ERR_UMODEUNKNOWNFLAG,
1960 Params: []string{dc.nick, "Cannot change user mode in multi-upstream mode"},
1961 })
1962 }
[46]1963 } else {
[553]1964 var userMode string
1965 if uc := dc.upstream(); uc != nil {
1966 userMode = string(uc.modes)
1967 }
1968
[55]1969 dc.SendMessage(&irc.Message{
1970 Prefix: dc.srv.prefix(),
[46]1971 Command: irc.RPL_UMODEIS,
[672]1972 Params: []string{dc.nick, "+" + userMode},
[54]1973 })
[46]1974 }
[139]1975 return nil
[46]1976 }
[139]1977
1978 uc, upstreamName, err := dc.unmarshalEntity(name)
1979 if err != nil {
1980 return err
1981 }
1982
1983 if !uc.isChannel(upstreamName) {
1984 return ircError{&irc.Message{
1985 Command: irc.ERR_USERSDONTMATCH,
1986 Params: []string{dc.nick, "Cannot change mode for other users"},
1987 }}
1988 }
1989
1990 if modeStr != "" {
1991 params := []string{upstreamName, modeStr}
1992 params = append(params, msg.Params[2:]...)
[757]1993 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[139]1994 Command: "MODE",
1995 Params: params,
1996 })
1997 } else {
[478]1998 ch := uc.channels.Value(upstreamName)
1999 if ch == nil {
[139]2000 return ircError{&irc.Message{
2001 Command: irc.ERR_NOSUCHCHANNEL,
2002 Params: []string{dc.nick, name, "No such channel"},
2003 }}
2004 }
2005
2006 if ch.modes == nil {
2007 // we haven't received the initial RPL_CHANNELMODEIS yet
2008 // ignore the request, we will broadcast the modes later when we receive RPL_CHANNELMODEIS
2009 return nil
2010 }
2011
2012 modeStr, modeParams := ch.modes.Format()
2013 params := []string{dc.nick, name, modeStr}
2014 params = append(params, modeParams...)
2015
2016 dc.SendMessage(&irc.Message{
2017 Prefix: dc.srv.prefix(),
2018 Command: irc.RPL_CHANNELMODEIS,
2019 Params: params,
2020 })
[162]2021 if ch.creationTime != "" {
2022 dc.SendMessage(&irc.Message{
2023 Prefix: dc.srv.prefix(),
2024 Command: rpl_creationtime,
2025 Params: []string{dc.nick, name, ch.creationTime},
2026 })
2027 }
[139]2028 }
[160]2029 case "TOPIC":
2030 var channel string
2031 if err := parseMessageParams(msg, &channel); err != nil {
2032 return err
2033 }
2034
[478]2035 uc, upstreamName, err := dc.unmarshalEntity(channel)
[160]2036 if err != nil {
2037 return err
2038 }
2039
2040 if len(msg.Params) > 1 { // setting topic
2041 topic := msg.Params[1]
[757]2042 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[160]2043 Command: "TOPIC",
[478]2044 Params: []string{upstreamName, topic},
[160]2045 })
2046 } else { // getting topic
[478]2047 ch := uc.channels.Value(upstreamName)
2048 if ch == nil {
[160]2049 return ircError{&irc.Message{
2050 Command: irc.ERR_NOSUCHCHANNEL,
[478]2051 Params: []string{dc.nick, upstreamName, "No such channel"},
[160]2052 }}
2053 }
2054 sendTopic(dc, ch)
2055 }
[177]2056 case "LIST":
[681]2057 network := dc.network
2058 if network == nil && len(msg.Params) > 0 {
2059 var err error
2060 network, msg.Params[0], err = dc.unmarshalEntityNetwork(msg.Params[0])
2061 if err != nil {
2062 return err
[177]2063 }
2064 }
[681]2065 if network == nil {
2066 dc.SendMessage(&irc.Message{
2067 Prefix: dc.srv.prefix(),
2068 Command: irc.RPL_LISTEND,
2069 Params: []string{dc.nick, "LIST without a network suffix is not supported in multi-upstream mode"},
2070 })
2071 return nil
2072 }
[177]2073
[681]2074 uc := network.conn
2075 if uc == nil {
2076 dc.SendMessage(&irc.Message{
2077 Prefix: dc.srv.prefix(),
2078 Command: irc.RPL_LISTEND,
2079 Params: []string{dc.nick, "Disconnected from upstream server"},
2080 })
2081 return nil
2082 }
2083
[682]2084 uc.enqueueCommand(dc, msg)
[140]2085 case "NAMES":
2086 if len(msg.Params) == 0 {
2087 dc.SendMessage(&irc.Message{
2088 Prefix: dc.srv.prefix(),
2089 Command: irc.RPL_ENDOFNAMES,
2090 Params: []string{dc.nick, "*", "End of /NAMES list"},
2091 })
2092 return nil
2093 }
2094
2095 channels := strings.Split(msg.Params[0], ",")
2096 for _, channel := range channels {
[478]2097 uc, upstreamName, err := dc.unmarshalEntity(channel)
[140]2098 if err != nil {
2099 return err
2100 }
2101
[478]2102 ch := uc.channels.Value(upstreamName)
2103 if ch != nil {
[140]2104 sendNames(dc, ch)
2105 } else {
2106 // NAMES on a channel we have not joined, ask upstream
[757]2107 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[140]2108 Command: "NAMES",
[478]2109 Params: []string{upstreamName},
[140]2110 })
2111 }
2112 }
[660]2113 // For WHOX docs, see:
2114 // - http://faerion.sourceforge.net/doc/irc/whox.var
2115 // - https://github.com/quakenet/snircd/blob/master/doc/readme.who
2116 // Note, many features aren't widely implemented, such as flags and mask2
[127]2117 case "WHO":
2118 if len(msg.Params) == 0 {
2119 // TODO: support WHO without parameters
2120 dc.SendMessage(&irc.Message{
2121 Prefix: dc.srv.prefix(),
2122 Command: irc.RPL_ENDOFWHO,
[140]2123 Params: []string{dc.nick, "*", "End of /WHO list"},
[127]2124 })
2125 return nil
2126 }
2127
[660]2128 // Clients will use the first mask to match RPL_ENDOFWHO
2129 endOfWhoToken := msg.Params[0]
[127]2130
[660]2131 // TODO: add support for WHOX mask2
2132 mask := msg.Params[0]
2133 var options string
2134 if len(msg.Params) > 1 {
2135 options = msg.Params[1]
2136 }
2137
2138 optionsParts := strings.SplitN(options, "%", 2)
2139 // TODO: add support for WHOX flags in optionsParts[0]
2140 var fields, whoxToken string
2141 if len(optionsParts) == 2 {
2142 optionsParts := strings.SplitN(optionsParts[1], ",", 2)
2143 fields = strings.ToLower(optionsParts[0])
2144 if len(optionsParts) == 2 && strings.Contains(fields, "t") {
2145 whoxToken = optionsParts[1]
2146 }
2147 }
2148
2149 // TODO: support mixed bouncer/upstream WHO queries
2150 maskCM := casemapASCII(mask)
2151 if dc.network == nil && maskCM == dc.nickCM {
[142]2152 // TODO: support AWAY (H/G) in self WHO reply
[658]2153 flags := "H"
2154 if dc.user.Admin {
[659]2155 flags += "*"
[658]2156 }
[660]2157 info := whoxInfo{
2158 Token: whoxToken,
2159 Username: dc.user.Username,
2160 Hostname: dc.hostname,
[691]2161 Server: dc.srv.Config().Hostname,
[660]2162 Nickname: dc.nick,
2163 Flags: flags,
[661]2164 Account: dc.user.Username,
[660]2165 Realname: dc.realname,
2166 }
2167 dc.SendMessage(generateWHOXReply(dc.srv.prefix(), dc.nick, fields, &info))
[142]2168 dc.SendMessage(&irc.Message{
2169 Prefix: dc.srv.prefix(),
2170 Command: irc.RPL_ENDOFWHO,
[660]2171 Params: []string{dc.nick, endOfWhoToken, "End of /WHO list"},
[142]2172 })
2173 return nil
2174 }
[660]2175 if maskCM == serviceNickCM {
2176 info := whoxInfo{
2177 Token: whoxToken,
2178 Username: servicePrefix.User,
2179 Hostname: servicePrefix.Host,
[691]2180 Server: dc.srv.Config().Hostname,
[660]2181 Nickname: serviceNick,
2182 Flags: "H*",
[661]2183 Account: serviceNick,
[660]2184 Realname: serviceRealname,
2185 }
2186 dc.SendMessage(generateWHOXReply(dc.srv.prefix(), dc.nick, fields, &info))
[343]2187 dc.SendMessage(&irc.Message{
2188 Prefix: dc.srv.prefix(),
2189 Command: irc.RPL_ENDOFWHO,
[660]2190 Params: []string{dc.nick, endOfWhoToken, "End of /WHO list"},
[343]2191 })
2192 return nil
2193 }
[142]2194
[660]2195 // TODO: properly support WHO masks
2196 uc, upstreamMask, err := dc.unmarshalEntity(mask)
[127]2197 if err != nil {
2198 return err
2199 }
2200
[660]2201 params := []string{upstreamMask}
2202 if options != "" {
2203 params = append(params, options)
[127]2204 }
2205
[682]2206 uc.enqueueCommand(dc, &irc.Message{
[127]2207 Command: "WHO",
2208 Params: params,
2209 })
[128]2210 case "WHOIS":
2211 if len(msg.Params) == 0 {
2212 return ircError{&irc.Message{
2213 Command: irc.ERR_NONICKNAMEGIVEN,
2214 Params: []string{dc.nick, "No nickname given"},
2215 }}
2216 }
2217
2218 var target, mask string
2219 if len(msg.Params) == 1 {
2220 target = ""
2221 mask = msg.Params[0]
2222 } else {
2223 target = msg.Params[0]
2224 mask = msg.Params[1]
2225 }
2226 // TODO: support multiple WHOIS users
2227 if i := strings.IndexByte(mask, ','); i >= 0 {
2228 mask = mask[:i]
2229 }
2230
[520]2231 if dc.network == nil && casemapASCII(mask) == dc.nickCM {
[142]2232 dc.SendMessage(&irc.Message{
2233 Prefix: dc.srv.prefix(),
2234 Command: irc.RPL_WHOISUSER,
[184]2235 Params: []string{dc.nick, dc.nick, dc.user.Username, dc.hostname, "*", dc.realname},
[142]2236 })
2237 dc.SendMessage(&irc.Message{
2238 Prefix: dc.srv.prefix(),
2239 Command: irc.RPL_WHOISSERVER,
[691]2240 Params: []string{dc.nick, dc.nick, dc.srv.Config().Hostname, "soju"},
[142]2241 })
[658]2242 if dc.user.Admin {
2243 dc.SendMessage(&irc.Message{
2244 Prefix: dc.srv.prefix(),
2245 Command: irc.RPL_WHOISOPERATOR,
2246 Params: []string{dc.nick, dc.nick, "is a bouncer administrator"},
2247 })
2248 }
[142]2249 dc.SendMessage(&irc.Message{
2250 Prefix: dc.srv.prefix(),
[661]2251 Command: rpl_whoisaccount,
2252 Params: []string{dc.nick, dc.nick, dc.user.Username, "is logged in as"},
2253 })
2254 dc.SendMessage(&irc.Message{
2255 Prefix: dc.srv.prefix(),
[142]2256 Command: irc.RPL_ENDOFWHOIS,
2257 Params: []string{dc.nick, dc.nick, "End of /WHOIS list"},
2258 })
2259 return nil
2260 }
[609]2261 if casemapASCII(mask) == serviceNickCM {
2262 dc.SendMessage(&irc.Message{
2263 Prefix: dc.srv.prefix(),
2264 Command: irc.RPL_WHOISUSER,
2265 Params: []string{dc.nick, serviceNick, servicePrefix.User, servicePrefix.Host, "*", serviceRealname},
2266 })
2267 dc.SendMessage(&irc.Message{
2268 Prefix: dc.srv.prefix(),
2269 Command: irc.RPL_WHOISSERVER,
[691]2270 Params: []string{dc.nick, serviceNick, dc.srv.Config().Hostname, "soju"},
[609]2271 })
2272 dc.SendMessage(&irc.Message{
2273 Prefix: dc.srv.prefix(),
[657]2274 Command: irc.RPL_WHOISOPERATOR,
2275 Params: []string{dc.nick, serviceNick, "is the bouncer service"},
2276 })
2277 dc.SendMessage(&irc.Message{
2278 Prefix: dc.srv.prefix(),
[661]2279 Command: rpl_whoisaccount,
2280 Params: []string{dc.nick, serviceNick, serviceNick, "is logged in as"},
2281 })
2282 dc.SendMessage(&irc.Message{
2283 Prefix: dc.srv.prefix(),
[609]2284 Command: irc.RPL_ENDOFWHOIS,
2285 Params: []string{dc.nick, serviceNick, "End of /WHOIS list"},
2286 })
2287 return nil
2288 }
[142]2289
[128]2290 // TODO: support WHOIS masks
2291 uc, upstreamNick, err := dc.unmarshalEntity(mask)
2292 if err != nil {
2293 return err
2294 }
2295
2296 var params []string
2297 if target != "" {
[299]2298 if target == mask { // WHOIS nick nick
2299 params = []string{upstreamNick, upstreamNick}
2300 } else {
2301 params = []string{target, upstreamNick}
2302 }
[128]2303 } else {
2304 params = []string{upstreamNick}
2305 }
2306
[757]2307 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[128]2308 Command: "WHOIS",
2309 Params: params,
2310 })
[562]2311 case "PRIVMSG", "NOTICE":
[58]2312 var targetsStr, text string
2313 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
2314 return err
2315 }
[303]2316 tags := copyClientTags(msg.Tags)
[58]2317
2318 for _, name := range strings.Split(targetsStr, ",") {
[691]2319 if name == "$"+dc.srv.Config().Hostname || (name == "$*" && dc.network == nil) {
[563]2320 // "$" means a server mask follows. If it's the bouncer's
2321 // hostname, broadcast the message to all bouncer users.
2322 if !dc.user.Admin {
2323 return ircError{&irc.Message{
2324 Prefix: dc.srv.prefix(),
2325 Command: irc.ERR_BADMASK,
2326 Params: []string{dc.nick, name, "Permission denied to broadcast message to all bouncer users"},
2327 }}
2328 }
2329
2330 dc.logger.Printf("broadcasting bouncer-wide %v: %v", msg.Command, text)
2331
2332 broadcastTags := tags.Copy()
[784]2333 broadcastTags["time"] = irc.TagValue(formatServerTime(time.Now()))
[563]2334 broadcastMsg := &irc.Message{
2335 Tags: broadcastTags,
2336 Prefix: servicePrefix,
2337 Command: msg.Command,
2338 Params: []string{name, text},
2339 }
2340 dc.srv.forEachUser(func(u *user) {
2341 u.events <- eventBroadcast{broadcastMsg}
2342 })
2343 continue
2344 }
2345
[529]2346 if dc.network == nil && casemapASCII(name) == dc.nickCM {
[618]2347 dc.SendMessage(&irc.Message{
2348 Tags: msg.Tags.Copy(),
2349 Prefix: dc.prefix(),
2350 Command: msg.Command,
2351 Params: []string{name, text},
2352 })
[529]2353 continue
2354 }
2355
[562]2356 if msg.Command == "PRIVMSG" && casemapASCII(name) == serviceNickCM {
[431]2357 if dc.caps["echo-message"] {
2358 echoTags := tags.Copy()
[784]2359 echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
[431]2360 dc.SendMessage(&irc.Message{
2361 Tags: echoTags,
2362 Prefix: dc.prefix(),
[562]2363 Command: msg.Command,
[431]2364 Params: []string{name, text},
2365 })
2366 }
[677]2367 handleServicePRIVMSG(ctx, dc, text)
[117]2368 continue
2369 }
2370
[127]2371 uc, upstreamName, err := dc.unmarshalEntity(name)
[58]2372 if err != nil {
2373 return err
2374 }
2375
[562]2376 if msg.Command == "PRIVMSG" && uc.network.casemap(upstreamName) == "nickserv" {
[675]2377 dc.handleNickServPRIVMSG(ctx, uc, text)
[95]2378 }
2379
[268]2380 unmarshaledText := text
2381 if uc.isChannel(upstreamName) {
2382 unmarshaledText = dc.unmarshalText(uc, text)
2383 }
[757]2384 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[303]2385 Tags: tags,
[562]2386 Command: msg.Command,
[268]2387 Params: []string{upstreamName, unmarshaledText},
[60]2388 })
[105]2389
[303]2390 echoTags := tags.Copy()
[784]2391 echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
[559]2392 if uc.account != "" {
2393 echoTags["account"] = irc.TagValue(uc.account)
2394 }
[113]2395 echoMsg := &irc.Message{
[690]2396 Tags: echoTags,
2397 Prefix: &irc.Prefix{Name: uc.nick},
[562]2398 Command: msg.Command,
[113]2399 Params: []string{upstreamName, text},
2400 }
[239]2401 uc.produce(upstreamName, echoMsg, dc)
[435]2402
2403 uc.updateChannelAutoDetach(upstreamName)
[58]2404 }
[303]2405 case "TAGMSG":
2406 var targetsStr string
2407 if err := parseMessageParams(msg, &targetsStr); err != nil {
2408 return err
2409 }
2410 tags := copyClientTags(msg.Tags)
2411
2412 for _, name := range strings.Split(targetsStr, ",") {
[617]2413 if dc.network == nil && casemapASCII(name) == dc.nickCM {
2414 dc.SendMessage(&irc.Message{
2415 Tags: msg.Tags.Copy(),
2416 Prefix: dc.prefix(),
2417 Command: "TAGMSG",
2418 Params: []string{name},
2419 })
2420 continue
2421 }
2422
[616]2423 if casemapASCII(name) == serviceNickCM {
2424 continue
2425 }
2426
[303]2427 uc, upstreamName, err := dc.unmarshalEntity(name)
2428 if err != nil {
2429 return err
2430 }
[427]2431 if _, ok := uc.caps["message-tags"]; !ok {
2432 continue
2433 }
[303]2434
[757]2435 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[303]2436 Tags: tags,
2437 Command: "TAGMSG",
2438 Params: []string{upstreamName},
2439 })
[435]2440
[780]2441 echoTags := tags.Copy()
[784]2442 echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
[780]2443 if uc.account != "" {
2444 echoTags["account"] = irc.TagValue(uc.account)
2445 }
2446 echoMsg := &irc.Message{
2447 Tags: echoTags,
2448 Prefix: &irc.Prefix{Name: uc.nick},
2449 Command: "TAGMSG",
2450 Params: []string{upstreamName},
2451 }
2452 uc.produce(upstreamName, echoMsg, dc)
2453
[435]2454 uc.updateChannelAutoDetach(upstreamName)
[303]2455 }
[163]2456 case "INVITE":
2457 var user, channel string
2458 if err := parseMessageParams(msg, &user, &channel); err != nil {
2459 return err
2460 }
2461
2462 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
2463 if err != nil {
2464 return err
2465 }
2466
2467 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
2468 if err != nil {
2469 return err
2470 }
2471
2472 if ucChannel != ucUser {
2473 return ircError{&irc.Message{
2474 Command: irc.ERR_USERNOTINCHANNEL,
[401]2475 Params: []string{dc.nick, user, channel, "They are on another network"},
[163]2476 }}
2477 }
2478 uc := ucChannel
2479
[757]2480 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[163]2481 Command: "INVITE",
2482 Params: []string{upstreamUser, upstreamChannel},
2483 })
[724]2484 case "AUTHENTICATE":
2485 // Post-connection-registration AUTHENTICATE is unsupported in
2486 // multi-upstream mode, or if the upstream doesn't support SASL
2487 uc := dc.upstream()
2488 if uc == nil || !uc.caps["sasl"] {
2489 return ircError{&irc.Message{
2490 Command: irc.ERR_SASLFAIL,
2491 Params: []string{dc.nick, "Upstream network authentication not supported"},
2492 }}
2493 }
2494
2495 credentials, err := dc.handleAuthenticateCommand(msg)
2496 if err != nil {
2497 return err
2498 }
2499
2500 if credentials != nil {
2501 if uc.saslClient != nil {
2502 dc.endSASL(&irc.Message{
2503 Prefix: dc.srv.prefix(),
2504 Command: irc.ERR_SASLFAIL,
2505 Params: []string{dc.nick, "Another authentication attempt is already in progress"},
2506 })
2507 return nil
2508 }
2509
2510 uc.logger.Printf("starting post-registration SASL PLAIN authentication with username %q", credentials.plainUsername)
2511 uc.saslClient = sasl.NewPlainClient("", credentials.plainUsername, credentials.plainPassword)
2512 uc.enqueueCommand(dc, &irc.Message{
2513 Command: "AUTHENTICATE",
2514 Params: []string{"PLAIN"},
2515 })
2516 }
[729]2517 case "REGISTER", "VERIFY":
2518 // Check number of params here, since we'll use that to save the
2519 // credentials on command success
2520 if (msg.Command == "REGISTER" && len(msg.Params) < 3) || (msg.Command == "VERIFY" && len(msg.Params) < 2) {
2521 return newNeedMoreParamsError(msg.Command)
2522 }
2523
2524 uc := dc.upstream()
2525 if uc == nil || !uc.caps["draft/account-registration"] {
2526 return ircError{&irc.Message{
2527 Command: "FAIL",
2528 Params: []string{msg.Command, "TEMPORARILY_UNAVAILABLE", "*", "Upstream network account registration not supported"},
2529 }}
2530 }
2531
2532 uc.logger.Printf("starting %v with account name %v", msg.Command, msg.Params[0])
2533 uc.enqueueCommand(dc, msg)
[684]2534 case "MONITOR":
2535 // MONITOR is unsupported in multi-upstream mode
2536 uc := dc.upstream()
2537 if uc == nil {
2538 return newUnknownCommandError(msg.Command)
2539 }
[742]2540 if _, ok := uc.isupport["MONITOR"]; !ok {
2541 return newUnknownCommandError(msg.Command)
2542 }
[684]2543
2544 var subcommand string
2545 if err := parseMessageParams(msg, &subcommand); err != nil {
2546 return err
2547 }
2548
2549 switch strings.ToUpper(subcommand) {
2550 case "+", "-":
2551 var targets string
2552 if err := parseMessageParams(msg, nil, &targets); err != nil {
2553 return err
2554 }
2555 for _, target := range strings.Split(targets, ",") {
2556 if subcommand == "+" {
2557 // Hard limit, just to avoid having downstreams fill our map
2558 if len(dc.monitored.innerMap) >= 1000 {
2559 dc.SendMessage(&irc.Message{
2560 Prefix: dc.srv.prefix(),
2561 Command: irc.ERR_MONLISTFULL,
2562 Params: []string{dc.nick, "1000", target, "Bouncer monitor list is full"},
2563 })
2564 continue
2565 }
2566
2567 dc.monitored.SetValue(target, nil)
2568
2569 if uc.monitored.Has(target) {
2570 cmd := irc.RPL_MONOFFLINE
2571 if online := uc.monitored.Value(target); online {
2572 cmd = irc.RPL_MONONLINE
2573 }
2574
2575 dc.SendMessage(&irc.Message{
2576 Prefix: dc.srv.prefix(),
2577 Command: cmd,
2578 Params: []string{dc.nick, target},
2579 })
2580 }
2581 } else {
2582 dc.monitored.Delete(target)
2583 }
2584 }
2585 uc.updateMonitor()
2586 case "C": // clear
2587 dc.monitored = newCasemapMap(0)
2588 uc.updateMonitor()
2589 case "L": // list
2590 // TODO: be less lazy and pack the list
2591 for _, entry := range dc.monitored.innerMap {
2592 dc.SendMessage(&irc.Message{
2593 Prefix: dc.srv.prefix(),
2594 Command: irc.RPL_MONLIST,
2595 Params: []string{dc.nick, entry.originalKey},
2596 })
2597 }
2598 dc.SendMessage(&irc.Message{
2599 Prefix: dc.srv.prefix(),
2600 Command: irc.RPL_ENDOFMONLIST,
2601 Params: []string{dc.nick, "End of MONITOR list"},
2602 })
2603 case "S": // status
2604 // TODO: be less lazy and pack the lists
2605 for _, entry := range dc.monitored.innerMap {
2606 target := entry.originalKey
2607
2608 cmd := irc.RPL_MONOFFLINE
2609 if online := uc.monitored.Value(target); online {
2610 cmd = irc.RPL_MONONLINE
2611 }
2612
2613 dc.SendMessage(&irc.Message{
2614 Prefix: dc.srv.prefix(),
2615 Command: cmd,
2616 Params: []string{dc.nick, target},
2617 })
2618 }
2619 }
[319]2620 case "CHATHISTORY":
2621 var subcommand string
2622 if err := parseMessageParams(msg, &subcommand); err != nil {
2623 return err
2624 }
[516]2625 var target, limitStr string
2626 var boundsStr [2]string
2627 switch subcommand {
[719]2628 case "AFTER", "BEFORE", "LATEST":
[516]2629 if err := parseMessageParams(msg, nil, &target, &boundsStr[0], &limitStr); err != nil {
2630 return err
2631 }
2632 case "BETWEEN":
2633 if err := parseMessageParams(msg, nil, &target, &boundsStr[0], &boundsStr[1], &limitStr); err != nil {
2634 return err
2635 }
[549]2636 case "TARGETS":
[688]2637 if dc.network == nil {
2638 // Either an unbound bouncer network, in which case we should return no targets,
2639 // or a multi-upstream downstream, but we don't support CHATHISTORY TARGETS for those yet.
2640 dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef irc.TagValue) {})
2641 return nil
2642 }
[549]2643 if err := parseMessageParams(msg, nil, &boundsStr[0], &boundsStr[1], &limitStr); err != nil {
2644 return err
2645 }
[516]2646 default:
[719]2647 // TODO: support AROUND
[319]2648 return ircError{&irc.Message{
2649 Command: "FAIL",
[516]2650 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, "Unknown command"},
[319]2651 }}
2652 }
2653
[586]2654 // We don't save history for our service
2655 if casemapASCII(target) == serviceNickCM {
2656 dc.SendBatch("chathistory", []string{target}, nil, func(batchRef irc.TagValue) {})
2657 return nil
2658 }
2659
[441]2660 store, ok := dc.user.msgStore.(chatHistoryMessageStore)
2661 if !ok {
[319]2662 return ircError{&irc.Message{
2663 Command: irc.ERR_UNKNOWNCOMMAND,
[456]2664 Params: []string{dc.nick, "CHATHISTORY", "Unknown command"},
[319]2665 }}
2666 }
2667
[585]2668 network, entity, err := dc.unmarshalEntityNetwork(target)
[319]2669 if err != nil {
2670 return err
2671 }
[585]2672 entity = network.casemap(entity)
[319]2673
2674 // TODO: support msgid criteria
[516]2675 var bounds [2]time.Time
2676 bounds[0] = parseChatHistoryBound(boundsStr[0])
[719]2677 if subcommand == "LATEST" && boundsStr[0] == "*" {
[720]2678 bounds[0] = time.Now()
[719]2679 } else if bounds[0].IsZero() {
[319]2680 return ircError{&irc.Message{
2681 Command: "FAIL",
[516]2682 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[0], "Invalid first bound"},
[319]2683 }}
2684 }
2685
[516]2686 if boundsStr[1] != "" {
2687 bounds[1] = parseChatHistoryBound(boundsStr[1])
2688 if bounds[1].IsZero() {
2689 return ircError{&irc.Message{
2690 Command: "FAIL",
2691 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[1], "Invalid second bound"},
2692 }}
2693 }
[319]2694 }
2695
2696 limit, err := strconv.Atoi(limitStr)
[670]2697 if err != nil || limit < 0 || limit > chatHistoryLimit {
[319]2698 return ircError{&irc.Message{
2699 Command: "FAIL",
[456]2700 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, limitStr, "Invalid limit"},
[319]2701 }}
2702 }
2703
[665]2704 eventPlayback := dc.caps["draft/event-playback"]
2705
[387]2706 var history []*irc.Message
[319]2707 switch subcommand {
[719]2708 case "BEFORE", "LATEST":
[667]2709 history, err = store.LoadBeforeTime(ctx, &network.Network, entity, bounds[0], time.Time{}, limit, eventPlayback)
[360]2710 case "AFTER":
[667]2711 history, err = store.LoadAfterTime(ctx, &network.Network, entity, bounds[0], time.Now(), limit, eventPlayback)
[516]2712 case "BETWEEN":
2713 if bounds[0].Before(bounds[1]) {
[667]2714 history, err = store.LoadAfterTime(ctx, &network.Network, entity, bounds[0], bounds[1], limit, eventPlayback)
[516]2715 } else {
[667]2716 history, err = store.LoadBeforeTime(ctx, &network.Network, entity, bounds[0], bounds[1], limit, eventPlayback)
[516]2717 }
[549]2718 case "TARGETS":
2719 // TODO: support TARGETS in multi-upstream mode
[667]2720 targets, err := store.ListTargets(ctx, &network.Network, bounds[0], bounds[1], limit, eventPlayback)
[549]2721 if err != nil {
[627]2722 dc.logger.Printf("failed fetching targets for chathistory: %v", err)
[549]2723 return ircError{&irc.Message{
2724 Command: "FAIL",
2725 Params: []string{"CHATHISTORY", "MESSAGE_ERROR", subcommand, "Failed to retrieve targets"},
2726 }}
2727 }
2728
[551]2729 dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef irc.TagValue) {
2730 for _, target := range targets {
[585]2731 if ch := network.channels.Value(target.Name); ch != nil && ch.Detached {
[551]2732 continue
2733 }
[549]2734
[551]2735 dc.SendMessage(&irc.Message{
2736 Tags: irc.Tags{"batch": batchRef},
2737 Prefix: dc.srv.prefix(),
2738 Command: "CHATHISTORY",
[784]2739 Params: []string{"TARGETS", target.Name, formatServerTime(target.LatestMessage)},
[551]2740 })
[550]2741 }
[549]2742 })
2743
2744 return nil
[319]2745 }
[387]2746 if err != nil {
[515]2747 dc.logger.Printf("failed fetching %q messages for chathistory: %v", target, err)
[387]2748 return newChatHistoryError(subcommand, target)
2749 }
2750
[551]2751 dc.SendBatch("chathistory", []string{target}, nil, func(batchRef irc.TagValue) {
2752 for _, msg := range history {
2753 msg.Tags["batch"] = batchRef
[585]2754 dc.SendMessage(dc.marshalMessage(msg, network))
[551]2755 }
[387]2756 })
[781]2757 case "READ":
2758 var target, criteria string
2759 if err := parseMessageParams(msg, &target); err != nil {
2760 return ircError{&irc.Message{
2761 Command: "FAIL",
2762 Params: []string{"READ", "NEED_MORE_PARAMS", "Missing parameters"},
2763 }}
2764 }
2765 if len(msg.Params) > 1 {
2766 criteria = msg.Params[1]
2767 }
2768
[783]2769 // We don't save read receipts for our service
2770 if casemapASCII(target) == serviceNickCM {
2771 dc.SendMessage(&irc.Message{
2772 Prefix: dc.prefix(),
2773 Command: "READ",
2774 Params: []string{target, "*"},
2775 })
2776 return nil
2777 }
2778
[781]2779 uc, entity, err := dc.unmarshalEntity(target)
2780 if err != nil {
2781 return err
2782 }
2783 entityCM := uc.network.casemap(entity)
2784
2785 r, err := dc.srv.db.GetReadReceipt(ctx, uc.network.ID, entityCM)
2786 if err != nil {
2787 dc.logger.Printf("failed to get the read receipt for %q: %v", entity, err)
2788 return ircError{&irc.Message{
2789 Command: "FAIL",
2790 Params: []string{"READ", "INTERNAL_ERROR", target, "Internal error"},
2791 }}
2792 } else if r == nil {
2793 r = &ReadReceipt{
2794 Target: entityCM,
2795 }
2796 }
2797
2798 broadcast := false
2799 if len(criteria) > 0 {
2800 // TODO: support msgid criteria
2801 criteriaParts := strings.SplitN(criteria, "=", 2)
2802 if len(criteriaParts) != 2 || criteriaParts[0] != "timestamp" {
2803 return ircError{&irc.Message{
2804 Command: "FAIL",
2805 Params: []string{"READ", "INVALID_PARAMS", criteria, "Unknown criteria"},
2806 }}
2807 }
2808
2809 timestamp, err := time.Parse(serverTimeLayout, criteriaParts[1])
2810 if err != nil {
2811 return ircError{&irc.Message{
2812 Command: "FAIL",
2813 Params: []string{"READ", "INVALID_PARAMS", criteria, "Invalid criteria"},
2814 }}
2815 }
2816 now := time.Now()
2817 if timestamp.After(now) {
2818 timestamp = now
2819 }
2820 if r.Timestamp.Before(timestamp) {
2821 r.Timestamp = timestamp
2822 if err := dc.srv.db.StoreReadReceipt(ctx, uc.network.ID, r); err != nil {
2823 dc.logger.Printf("failed to store receipt for %q: %v", entity, err)
2824 return ircError{&irc.Message{
2825 Command: "FAIL",
2826 Params: []string{"READ", "INTERNAL_ERROR", target, "Internal error"},
2827 }}
2828 }
2829 broadcast = true
2830 }
2831 }
2832
2833 timestampStr := "*"
2834 if !r.Timestamp.IsZero() {
[784]2835 timestampStr = fmt.Sprintf("timestamp=%s", formatServerTime(r.Timestamp))
[781]2836 }
2837 uc.forEachDownstream(func(d *downstreamConn) {
2838 if broadcast || dc.id == d.id {
2839 d.SendMessage(&irc.Message{
2840 Prefix: d.prefix(),
2841 Command: "READ",
2842 Params: []string{d.marshalEntity(uc.network, entity), timestampStr},
2843 })
2844 }
2845 })
[532]2846 case "BOUNCER":
2847 var subcommand string
2848 if err := parseMessageParams(msg, &subcommand); err != nil {
2849 return err
2850 }
2851
2852 switch strings.ToUpper(subcommand) {
[646]2853 case "BIND":
2854 return ircError{&irc.Message{
2855 Command: "FAIL",
2856 Params: []string{"BOUNCER", "REGISTRATION_IS_COMPLETED", "BIND", "Cannot bind to a network after registration"},
2857 }}
[532]2858 case "LISTNETWORKS":
[551]2859 dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
[768]2860 for _, network := range dc.user.networks {
[551]2861 idStr := fmt.Sprintf("%v", network.ID)
2862 attrs := getNetworkAttrs(network)
2863 dc.SendMessage(&irc.Message{
2864 Tags: irc.Tags{"batch": batchRef},
2865 Prefix: dc.srv.prefix(),
2866 Command: "BOUNCER",
2867 Params: []string{"NETWORK", idStr, attrs.String()},
2868 })
[768]2869 }
[532]2870 })
2871 case "ADDNETWORK":
2872 var attrsStr string
2873 if err := parseMessageParams(msg, nil, &attrsStr); err != nil {
2874 return err
2875 }
2876 attrs := irc.ParseTags(attrsStr)
2877
[654]2878 record := &Network{Nick: dc.nick, Enabled: true}
2879 if err := updateNetworkAttrs(record, attrs, subcommand); err != nil {
2880 return err
[532]2881 }
2882
[664]2883 if record.Nick == dc.user.Username {
2884 record.Nick = ""
2885 }
[654]2886 if record.Realname == dc.user.Realname {
2887 record.Realname = ""
[532]2888 }
2889
[676]2890 network, err := dc.user.createNetwork(ctx, record)
[532]2891 if err != nil {
2892 return ircError{&irc.Message{
2893 Command: "FAIL",
2894 Params: []string{"BOUNCER", "UNKNOWN_ERROR", subcommand, fmt.Sprintf("Failed to create network: %v", err)},
2895 }}
2896 }
2897
2898 dc.SendMessage(&irc.Message{
2899 Prefix: dc.srv.prefix(),
2900 Command: "BOUNCER",
2901 Params: []string{"ADDNETWORK", fmt.Sprintf("%v", network.ID)},
2902 })
2903 case "CHANGENETWORK":
2904 var idStr, attrsStr string
2905 if err := parseMessageParams(msg, nil, &idStr, &attrsStr); err != nil {
2906 return err
2907 }
[535]2908 id, err := parseBouncerNetID(subcommand, idStr)
[532]2909 if err != nil {
2910 return err
2911 }
2912 attrs := irc.ParseTags(attrsStr)
2913
2914 net := dc.user.getNetworkByID(id)
2915 if net == nil {
2916 return ircError{&irc.Message{
2917 Command: "FAIL",
[535]2918 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, idStr, "Invalid network ID"},
[532]2919 }}
2920 }
2921
2922 record := net.Network // copy network record because we'll mutate it
[654]2923 if err := updateNetworkAttrs(&record, attrs, subcommand); err != nil {
2924 return err
[532]2925 }
2926
[664]2927 if record.Nick == dc.user.Username {
2928 record.Nick = ""
2929 }
[654]2930 if record.Realname == dc.user.Realname {
2931 record.Realname = ""
2932 }
2933
[676]2934 _, err = dc.user.updateNetwork(ctx, &record)
[532]2935 if err != nil {
2936 return ircError{&irc.Message{
2937 Command: "FAIL",
2938 Params: []string{"BOUNCER", "UNKNOWN_ERROR", subcommand, fmt.Sprintf("Failed to update network: %v", err)},
2939 }}
2940 }
2941
2942 dc.SendMessage(&irc.Message{
2943 Prefix: dc.srv.prefix(),
2944 Command: "BOUNCER",
2945 Params: []string{"CHANGENETWORK", idStr},
2946 })
2947 case "DELNETWORK":
2948 var idStr string
2949 if err := parseMessageParams(msg, nil, &idStr); err != nil {
2950 return err
2951 }
[535]2952 id, err := parseBouncerNetID(subcommand, idStr)
[532]2953 if err != nil {
2954 return err
2955 }
2956
2957 net := dc.user.getNetworkByID(id)
2958 if net == nil {
2959 return ircError{&irc.Message{
2960 Command: "FAIL",
[535]2961 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, idStr, "Invalid network ID"},
[532]2962 }}
2963 }
2964
[676]2965 if err := dc.user.deleteNetwork(ctx, net.ID); err != nil {
[532]2966 return err
2967 }
2968
2969 dc.SendMessage(&irc.Message{
2970 Prefix: dc.srv.prefix(),
2971 Command: "BOUNCER",
2972 Params: []string{"DELNETWORK", idStr},
2973 })
2974 default:
2975 return ircError{&irc.Message{
2976 Command: "FAIL",
2977 Params: []string{"BOUNCER", "UNKNOWN_COMMAND", subcommand, "Unknown subcommand"},
2978 }}
2979 }
[13]2980 default:
[55]2981 dc.logger.Printf("unhandled message: %v", msg)
[547]2982
2983 // Only forward unknown commands in single-upstream mode
2984 uc := dc.upstream()
2985 if uc == nil {
2986 return newUnknownCommandError(msg.Command)
2987 }
2988
[757]2989 uc.SendMessageLabeled(ctx, dc.id, msg)
[13]2990 }
[42]2991 return nil
[13]2992}
[95]2993
[675]2994func (dc *downstreamConn) handleNickServPRIVMSG(ctx context.Context, uc *upstreamConn, text string) {
[95]2995 username, password, ok := parseNickServCredentials(text, uc.nick)
[724]2996 if ok {
2997 uc.network.autoSaveSASLPlain(ctx, username, password)
[95]2998 }
2999}
3000
3001func parseNickServCredentials(text, nick string) (username, password string, ok bool) {
3002 fields := strings.Fields(text)
3003 if len(fields) < 2 {
3004 return "", "", false
3005 }
3006 cmd := strings.ToUpper(fields[0])
3007 params := fields[1:]
3008 switch cmd {
3009 case "REGISTER":
3010 username = nick
3011 password = params[0]
3012 case "IDENTIFY":
3013 if len(params) == 1 {
3014 username = nick
[182]3015 password = params[0]
[95]3016 } else {
3017 username = params[0]
[182]3018 password = params[1]
[95]3019 }
[182]3020 case "SET":
3021 if len(params) == 2 && strings.EqualFold(params[0], "PASSWORD") {
3022 username = nick
3023 password = params[1]
3024 }
[340]3025 default:
3026 return "", "", false
[95]3027 }
3028 return username, password, true
3029}
Note: See TracBrowser for help on using the repository browser.