source: code/trunk/downstream.go@ 790

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

downstream: fail on client or network name mismatch

This probably indicates a mis-configuration.

File size: 78.0 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
[789]1290 _, fallbackClientName, fallbackNetworkName := unmarshalUsername(dc.rawUsername)
1291 if dc.clientName == "" {
1292 dc.clientName = fallbackClientName
[790]1293 } else if dc.clientName != fallbackClientName {
1294 return ircError{&irc.Message{
1295 Command: irc.ERR_ERRONEUSNICKNAME,
1296 Params: []string{dc.nick, "Client name mismatch in usernames"},
1297 }}
[168]1298 }
[789]1299 if dc.networkName == "" {
1300 dc.networkName = fallbackNetworkName
[790]1301 } else if dc.networkName != fallbackNetworkName {
1302 return ircError{&irc.Message{
1303 Command: irc.ERR_ERRONEUSNICKNAME,
1304 Params: []string{dc.nick, "Network name mismatch in usernames"},
1305 }}
[789]1306 }
[168]1307
1308 dc.registered = true
[184]1309 dc.logger.Printf("registration complete for user %q", dc.user.Username)
[168]1310 return nil
1311}
1312
[701]1313func (dc *downstreamConn) loadNetwork(ctx context.Context) error {
[168]1314 if dc.networkName == "" {
[112]1315 return nil
1316 }
[85]1317
[168]1318 network := dc.user.getNetwork(dc.networkName)
[112]1319 if network == nil {
[168]1320 addr := dc.networkName
[112]1321 if !strings.ContainsRune(addr, ':') {
1322 addr = addr + ":6697"
1323 }
1324
1325 dc.logger.Printf("trying to connect to new network %q", addr)
[701]1326 if err := sanityCheckServer(ctx, addr); err != nil {
[112]1327 dc.logger.Printf("failed to connect to %q: %v", addr, err)
1328 return ircError{&irc.Message{
1329 Command: irc.ERR_PASSWDMISMATCH,
[754]1330 Params: []string{dc.nick, fmt.Sprintf("Failed to connect to %q", dc.networkName)},
[112]1331 }}
1332 }
1333
[354]1334 // Some clients only allow specifying the nickname (and use the
1335 // nickname as a username too). Strip the network name from the
1336 // nickname when auto-saving networks.
1337 nick, _, _ := unmarshalUsername(dc.nick)
1338
[168]1339 dc.logger.Printf("auto-saving network %q", dc.networkName)
[112]1340 var err error
[701]1341 network, err = dc.user.createNetwork(ctx, &Network{
[542]1342 Addr: dc.networkName,
1343 Nick: nick,
1344 Enabled: true,
[120]1345 })
[112]1346 if err != nil {
1347 return err
1348 }
1349 }
1350
1351 dc.network = network
1352 return nil
1353}
1354
[701]1355func (dc *downstreamConn) welcome(ctx context.Context) error {
[168]1356 if dc.user == nil || !dc.registered {
1357 panic("tried to welcome an unregistered connection")
[37]1358 }
1359
[750]1360 remoteAddr := dc.conn.RemoteAddr().String()
1361 dc.logger = &prefixLogger{dc.srv.Logger, fmt.Sprintf("user %q: downstream %q: ", dc.user.Username, remoteAddr)}
1362
[168]1363 // TODO: doing this might take some time. We should do it in dc.register
1364 // instead, but we'll potentially be adding a new network and this must be
1365 // done in the user goroutine.
[701]1366 if err := dc.loadNetwork(ctx); err != nil {
[168]1367 return err
[85]1368 }
1369
[694]1370 if dc.network == nil && !dc.caps["soju.im/bouncer-networks"] && dc.srv.Config().MultiUpstream {
[693]1371 dc.isMultiUpstream = true
1372 }
1373
[706]1374 dc.updateSupportedCaps()
1375
[446]1376 isupport := []string{
[670]1377 fmt.Sprintf("CHATHISTORY=%v", chatHistoryLimit),
[478]1378 "CASEMAPPING=ascii",
[446]1379 }
1380
[532]1381 if dc.network != nil {
1382 isupport = append(isupport, fmt.Sprintf("BOUNCER_NETID=%v", dc.network.ID))
1383 }
[691]1384 if title := dc.srv.Config().Title; dc.network == nil && title != "" {
1385 isupport = append(isupport, "NETWORK="+encodeISUPPORT(title))
[662]1386 }
[693]1387 if dc.network == nil && !dc.isMultiUpstream {
[660]1388 isupport = append(isupport, "WHOX")
1389 }
1390
[463]1391 if uc := dc.upstream(); uc != nil {
1392 for k := range passthroughIsupport {
1393 v, ok := uc.isupport[k]
1394 if !ok {
1395 continue
1396 }
1397 if v != nil {
1398 isupport = append(isupport, fmt.Sprintf("%v=%v", k, *v))
1399 } else {
1400 isupport = append(isupport, k)
1401 }
1402 }
[447]1403 }
1404
[55]1405 dc.SendMessage(&irc.Message{
1406 Prefix: dc.srv.prefix(),
[13]1407 Command: irc.RPL_WELCOME,
[98]1408 Params: []string{dc.nick, "Welcome to soju, " + dc.nick},
[54]1409 })
[55]1410 dc.SendMessage(&irc.Message{
1411 Prefix: dc.srv.prefix(),
[13]1412 Command: irc.RPL_YOURHOST,
[691]1413 Params: []string{dc.nick, "Your host is " + dc.srv.Config().Hostname},
[54]1414 })
[55]1415 dc.SendMessage(&irc.Message{
1416 Prefix: dc.srv.prefix(),
[13]1417 Command: irc.RPL_MYINFO,
[691]1418 Params: []string{dc.nick, dc.srv.Config().Hostname, "soju", "aiwroO", "OovaimnqpsrtklbeI"},
[54]1419 })
[463]1420 for _, msg := range generateIsupport(dc.srv.prefix(), dc.nick, isupport) {
1421 dc.SendMessage(msg)
1422 }
[553]1423 if uc := dc.upstream(); uc != nil {
1424 dc.SendMessage(&irc.Message{
1425 Prefix: dc.srv.prefix(),
1426 Command: irc.RPL_UMODEIS,
[672]1427 Params: []string{dc.nick, "+" + string(uc.modes)},
[553]1428 })
1429 }
[693]1430 if dc.network == nil && !dc.isMultiUpstream && dc.user.Admin {
[671]1431 dc.SendMessage(&irc.Message{
1432 Prefix: dc.srv.prefix(),
1433 Command: irc.RPL_UMODEIS,
1434 Params: []string{dc.nick, "+o"},
1435 })
1436 }
[13]1437
[706]1438 dc.updateNick()
1439 dc.updateRealname()
[722]1440 dc.updateAccount()
[706]1441
[691]1442 if motd := dc.user.srv.Config().MOTD; motd != "" && dc.network == nil {
[636]1443 for _, msg := range generateMOTD(dc.srv.prefix(), dc.nick, motd) {
1444 dc.SendMessage(msg)
1445 }
1446 } else {
1447 motdHint := "No MOTD"
1448 if dc.network != nil {
1449 motdHint = "Use /motd to read the message of the day"
1450 }
1451 dc.SendMessage(&irc.Message{
1452 Prefix: dc.srv.prefix(),
1453 Command: irc.ERR_NOMOTD,
1454 Params: []string{dc.nick, motdHint},
1455 })
1456 }
1457
[535]1458 if dc.caps["soju.im/bouncer-networks-notify"] {
[551]1459 dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
[768]1460 for _, network := range dc.user.networks {
[551]1461 idStr := fmt.Sprintf("%v", network.ID)
1462 attrs := getNetworkAttrs(network)
1463 dc.SendMessage(&irc.Message{
1464 Tags: irc.Tags{"batch": batchRef},
1465 Prefix: dc.srv.prefix(),
1466 Command: "BOUNCER",
1467 Params: []string{"NETWORK", idStr, attrs.String()},
1468 })
[768]1469 }
[535]1470 })
1471 }
1472
[73]1473 dc.forEachUpstream(func(uc *upstreamConn) {
[478]1474 for _, entry := range uc.channels.innerMap {
1475 ch := entry.value.(*upstreamChannel)
[284]1476 if !ch.complete {
1477 continue
1478 }
[478]1479 record := uc.network.channels.Value(ch.Name)
1480 if record != nil && record.Detached {
[284]1481 continue
1482 }
[132]1483
[284]1484 dc.SendMessage(&irc.Message{
1485 Prefix: dc.prefix(),
1486 Command: "JOIN",
1487 Params: []string{dc.marshalEntity(ch.conn.network, ch.Name)},
1488 })
1489
[781]1490 forwardChannel(ctx, dc, ch)
[30]1491 }
[143]1492 })
[50]1493
[143]1494 dc.forEachNetwork(func(net *network) {
[496]1495 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
1496 return
1497 }
1498
[253]1499 // Only send history if we're the first connected client with that name
1500 // for the network
[482]1501 firstClient := true
1502 dc.user.forEachDownstream(func(c *downstreamConn) {
1503 if c != dc && c.clientName == dc.clientName && c.network == dc.network {
1504 firstClient = false
1505 }
1506 })
1507 if firstClient {
[485]1508 net.delivered.ForEachTarget(func(target string) {
[495]1509 lastDelivered := net.delivered.LoadID(target, dc.clientName)
1510 if lastDelivered == "" {
1511 return
1512 }
1513
[701]1514 dc.sendTargetBacklog(ctx, net, target, lastDelivered)
[495]1515
1516 // Fast-forward history to last message
1517 targetCM := net.casemap(target)
[666]1518 lastID, err := dc.user.msgStore.LastMsgID(&net.Network, targetCM, time.Now())
[495]1519 if err != nil {
1520 dc.logger.Printf("failed to get last message ID: %v", err)
1521 return
1522 }
1523 net.delivered.StoreID(target, dc.clientName, lastID)
[485]1524 })
[227]1525 }
[253]1526 })
[57]1527
[253]1528 return nil
1529}
[144]1530
[665]1531// messageSupportsBacklog checks whether the provided message can be sent as
[428]1532// part of an history batch.
[665]1533func (dc *downstreamConn) messageSupportsBacklog(msg *irc.Message) bool {
[428]1534 // Don't replay all messages, because that would mess up client
1535 // state. For instance we just sent the list of users, sending
1536 // PART messages for one of these users would be incorrect.
1537 switch msg.Command {
1538 case "PRIVMSG", "NOTICE":
1539 return true
1540 }
1541 return false
1542}
1543
[701]1544func (dc *downstreamConn) sendTargetBacklog(ctx context.Context, net *network, target, msgID string) {
[423]1545 if dc.caps["draft/chathistory"] || dc.user.msgStore == nil {
[319]1546 return
1547 }
[485]1548
[499]1549 ch := net.channels.Value(target)
1550
[701]1551 ctx, cancel := context.WithTimeout(ctx, backlogTimeout)
[667]1552 defer cancel()
1553
[484]1554 targetCM := net.casemap(target)
[670]1555 history, err := dc.user.msgStore.LoadLatestID(ctx, &net.Network, targetCM, msgID, backlogLimit)
[452]1556 if err != nil {
[495]1557 dc.logger.Printf("failed to send backlog for %q: %v", target, err)
[452]1558 return
1559 }
[253]1560
[551]1561 dc.SendBatch("chathistory", []string{dc.marshalEntity(net, target)}, nil, func(batchRef irc.TagValue) {
1562 for _, msg := range history {
1563 if ch != nil && ch.Detached {
1564 if net.detachedMessageNeedsRelay(ch, msg) {
1565 dc.relayDetachedMessage(net, msg)
1566 }
1567 } else {
[651]1568 msg.Tags["batch"] = batchRef
[551]1569 dc.SendMessage(dc.marshalMessage(msg, net))
[499]1570 }
[256]1571 }
[551]1572 })
[13]1573}
1574
[499]1575func (dc *downstreamConn) relayDetachedMessage(net *network, msg *irc.Message) {
1576 if msg.Command != "PRIVMSG" && msg.Command != "NOTICE" {
1577 return
1578 }
1579
1580 sender := msg.Prefix.Name
1581 target, text := msg.Params[0], msg.Params[1]
1582 if net.isHighlight(msg) {
1583 sendServiceNOTICE(dc, fmt.Sprintf("highlight in %v: <%v> %v", dc.marshalEntity(net, target), sender, text))
1584 } else {
1585 sendServiceNOTICE(dc, fmt.Sprintf("message in %v: <%v> %v", dc.marshalEntity(net, target), sender, text))
1586 }
1587}
1588
[103]1589func (dc *downstreamConn) runUntilRegistered() error {
[704]1590 ctx, cancel := context.WithTimeout(context.TODO(), downstreamRegisterTimeout)
1591 defer cancel()
1592
1593 // Close the connection with an error if the deadline is exceeded
1594 go func() {
1595 <-ctx.Done()
1596 if err := ctx.Err(); err == context.DeadlineExceeded {
1597 dc.SendMessage(&irc.Message{
1598 Prefix: dc.srv.prefix(),
1599 Command: "ERROR",
1600 Params: []string{"Connection registration timed out"},
1601 })
1602 dc.Close()
1603 }
1604 }()
1605
[103]1606 for !dc.registered {
[212]1607 msg, err := dc.ReadMessage()
[106]1608 if err != nil {
[655]1609 return fmt.Errorf("failed to read IRC command: %w", err)
[103]1610 }
1611
[704]1612 err = dc.handleMessage(ctx, msg)
[103]1613 if ircErr, ok := err.(ircError); ok {
1614 ircErr.Message.Prefix = dc.srv.prefix()
1615 dc.SendMessage(ircErr.Message)
1616 } else if err != nil {
1617 return fmt.Errorf("failed to handle IRC command %q: %v", msg, err)
1618 }
1619 }
1620
1621 return nil
1622}
1623
[702]1624func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.Message) error {
[13]1625 switch msg.Command {
[111]1626 case "CAP":
1627 var subCmd string
1628 if err := parseMessageParams(msg, &subCmd); err != nil {
1629 return err
1630 }
1631 if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
1632 return err
1633 }
[107]1634 case "PING":
[412]1635 var source, destination string
1636 if err := parseMessageParams(msg, &source); err != nil {
1637 return err
1638 }
1639 if len(msg.Params) > 1 {
1640 destination = msg.Params[1]
1641 }
[691]1642 hostname := dc.srv.Config().Hostname
1643 if destination != "" && destination != hostname {
[412]1644 return ircError{&irc.Message{
1645 Command: irc.ERR_NOSUCHSERVER,
[413]1646 Params: []string{dc.nick, destination, "No such server"},
[412]1647 }}
1648 }
[107]1649 dc.SendMessage(&irc.Message{
1650 Prefix: dc.srv.prefix(),
1651 Command: "PONG",
[691]1652 Params: []string{hostname, source},
[107]1653 })
1654 return nil
[428]1655 case "PONG":
1656 if len(msg.Params) == 0 {
1657 return newNeedMoreParamsError(msg.Command)
1658 }
1659 token := msg.Params[len(msg.Params)-1]
1660 dc.handlePong(token)
[42]1661 case "USER":
[13]1662 return ircError{&irc.Message{
1663 Command: irc.ERR_ALREADYREGISTERED,
[55]1664 Params: []string{dc.nick, "You may not reregister"},
[13]1665 }}
[42]1666 case "NICK":
[429]1667 var rawNick string
1668 if err := parseMessageParams(msg, &rawNick); err != nil {
[90]1669 return err
1670 }
1671
[429]1672 nick := rawNick
[297]1673 var upstream *upstreamConn
1674 if dc.upstream() == nil {
1675 uc, unmarshaledNick, err := dc.unmarshalEntity(nick)
1676 if err == nil { // NICK nick/network: NICK only on a specific upstream
1677 upstream = uc
1678 nick = unmarshaledNick
1679 }
1680 }
1681
[717]1682 if nick == "" || strings.ContainsAny(nick, illegalNickChars) {
[404]1683 return ircError{&irc.Message{
1684 Command: irc.ERR_ERRONEUSNICKNAME,
[430]1685 Params: []string{dc.nick, rawNick, "contains illegal characters"},
[404]1686 }}
1687 }
[478]1688 if casemapASCII(nick) == serviceNickCM {
[429]1689 return ircError{&irc.Message{
1690 Command: irc.ERR_NICKNAMEINUSE,
1691 Params: []string{dc.nick, rawNick, "Nickname reserved for bouncer service"},
1692 }}
1693 }
[404]1694
[90]1695 var err error
1696 dc.forEachNetwork(func(n *network) {
[297]1697 if err != nil || (upstream != nil && upstream.network != n) {
[90]1698 return
1699 }
1700 n.Nick = nick
[675]1701 err = dc.srv.db.StoreNetwork(ctx, dc.user.ID, &n.Network)
[90]1702 })
1703 if err != nil {
1704 return err
1705 }
1706
[73]1707 dc.forEachUpstream(func(uc *upstreamConn) {
[297]1708 if upstream != nil && upstream != uc {
1709 return
1710 }
[757]1711 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[297]1712 Command: "NICK",
1713 Params: []string{nick},
1714 })
[42]1715 })
[296]1716
[512]1717 if dc.upstream() == nil && upstream == nil && dc.nick != nick {
[296]1718 dc.SendMessage(&irc.Message{
1719 Prefix: dc.prefix(),
1720 Command: "NICK",
1721 Params: []string{nick},
1722 })
1723 dc.nick = nick
[478]1724 dc.nickCM = casemapASCII(dc.nick)
[296]1725 }
[540]1726 case "SETNAME":
1727 var realname string
1728 if err := parseMessageParams(msg, &realname); err != nil {
1729 return err
1730 }
1731
[568]1732 // If the client just resets to the default, just wipe the per-network
1733 // preference
1734 storeRealname := realname
1735 if realname == dc.user.Realname {
1736 storeRealname = ""
1737 }
1738
[540]1739 var storeErr error
1740 var needUpdate []Network
1741 dc.forEachNetwork(func(n *network) {
1742 // We only need to call updateNetwork for upstreams that don't
1743 // support setname
1744 if uc := n.conn; uc != nil && uc.caps["setname"] {
[757]1745 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[540]1746 Command: "SETNAME",
1747 Params: []string{realname},
1748 })
1749
[568]1750 n.Realname = storeRealname
[675]1751 if err := dc.srv.db.StoreNetwork(ctx, dc.user.ID, &n.Network); err != nil {
[540]1752 dc.logger.Printf("failed to store network realname: %v", err)
1753 storeErr = err
1754 }
1755 return
1756 }
1757
1758 record := n.Network // copy network record because we'll mutate it
[568]1759 record.Realname = storeRealname
[540]1760 needUpdate = append(needUpdate, record)
1761 })
1762
1763 // Walk the network list as a second step, because updateNetwork
1764 // mutates the original list
1765 for _, record := range needUpdate {
[676]1766 if _, err := dc.user.updateNetwork(ctx, &record); err != nil {
[540]1767 dc.logger.Printf("failed to update network realname: %v", err)
1768 storeErr = err
1769 }
1770 }
1771 if storeErr != nil {
1772 return ircError{&irc.Message{
1773 Command: "FAIL",
1774 Params: []string{"SETNAME", "CANNOT_CHANGE_REALNAME", "Failed to update realname"},
1775 }}
1776 }
1777
[651]1778 if dc.upstream() == nil {
[540]1779 dc.SendMessage(&irc.Message{
1780 Prefix: dc.prefix(),
1781 Command: "SETNAME",
1782 Params: []string{realname},
1783 })
1784 }
[146]1785 case "JOIN":
1786 var namesStr string
1787 if err := parseMessageParams(msg, &namesStr); err != nil {
[48]1788 return err
1789 }
1790
[146]1791 var keys []string
1792 if len(msg.Params) > 1 {
1793 keys = strings.Split(msg.Params[1], ",")
1794 }
1795
1796 for i, name := range strings.Split(namesStr, ",") {
[145]1797 uc, upstreamName, err := dc.unmarshalEntity(name)
1798 if err != nil {
[158]1799 return err
[145]1800 }
[48]1801
[146]1802 var key string
1803 if len(keys) > i {
1804 key = keys[i]
1805 }
1806
[545]1807 if !uc.isChannel(upstreamName) {
1808 dc.SendMessage(&irc.Message{
1809 Prefix: dc.srv.prefix(),
1810 Command: irc.ERR_NOSUCHCHANNEL,
1811 Params: []string{name, "Not a channel name"},
1812 })
1813 continue
1814 }
1815
[758]1816 // Most servers ignore duplicate JOIN messages. We ignore them here
1817 // because some clients automatically send JOIN messages in bulk
1818 // when reconnecting to the bouncer. We don't want to flood the
1819 // upstream connection with these.
1820 if !uc.channels.Has(upstreamName) {
1821 params := []string{upstreamName}
1822 if key != "" {
1823 params = append(params, key)
1824 }
1825 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
1826 Command: "JOIN",
1827 Params: params,
1828 })
[146]1829 }
[89]1830
[478]1831 ch := uc.network.channels.Value(upstreamName)
1832 if ch != nil {
[285]1833 // Don't clear the channel key if there's one set
1834 // TODO: add a way to unset the channel key
[435]1835 if key != "" {
1836 ch.Key = key
1837 }
[781]1838 uc.network.attach(ctx, ch)
[435]1839 } else {
1840 ch = &Channel{
1841 Name: upstreamName,
1842 Key: key,
1843 }
[478]1844 uc.network.channels.SetValue(upstreamName, ch)
[285]1845 }
[675]1846 if err := dc.srv.db.StoreChannel(ctx, uc.network.ID, ch); err != nil {
[222]1847 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
[89]1848 }
1849 }
[146]1850 case "PART":
1851 var namesStr string
1852 if err := parseMessageParams(msg, &namesStr); err != nil {
1853 return err
1854 }
1855
1856 var reason string
1857 if len(msg.Params) > 1 {
1858 reason = msg.Params[1]
1859 }
1860
1861 for _, name := range strings.Split(namesStr, ",") {
1862 uc, upstreamName, err := dc.unmarshalEntity(name)
1863 if err != nil {
[158]1864 return err
[146]1865 }
1866
[284]1867 if strings.EqualFold(reason, "detach") {
[478]1868 ch := uc.network.channels.Value(upstreamName)
1869 if ch != nil {
[435]1870 uc.network.detach(ch)
1871 } else {
1872 ch = &Channel{
1873 Name: name,
1874 Detached: true,
1875 }
[478]1876 uc.network.channels.SetValue(upstreamName, ch)
[284]1877 }
[675]1878 if err := dc.srv.db.StoreChannel(ctx, uc.network.ID, ch); err != nil {
[435]1879 dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
1880 }
[284]1881 } else {
1882 params := []string{upstreamName}
1883 if reason != "" {
1884 params = append(params, reason)
1885 }
[757]1886 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[284]1887 Command: "PART",
1888 Params: params,
1889 })
[146]1890
[676]1891 if err := uc.network.deleteChannel(ctx, upstreamName); err != nil {
[284]1892 dc.logger.Printf("failed to delete channel %q: %v", upstreamName, err)
1893 }
[146]1894 }
1895 }
[159]1896 case "KICK":
1897 var channelStr, userStr string
1898 if err := parseMessageParams(msg, &channelStr, &userStr); err != nil {
1899 return err
1900 }
1901
1902 channels := strings.Split(channelStr, ",")
1903 users := strings.Split(userStr, ",")
1904
1905 var reason string
1906 if len(msg.Params) > 2 {
1907 reason = msg.Params[2]
1908 }
1909
1910 if len(channels) != 1 && len(channels) != len(users) {
1911 return ircError{&irc.Message{
1912 Command: irc.ERR_BADCHANMASK,
1913 Params: []string{dc.nick, channelStr, "Bad channel mask"},
1914 }}
1915 }
1916
1917 for i, user := range users {
1918 var channel string
1919 if len(channels) == 1 {
1920 channel = channels[0]
1921 } else {
1922 channel = channels[i]
1923 }
1924
1925 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
1926 if err != nil {
1927 return err
1928 }
1929
1930 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
1931 if err != nil {
1932 return err
1933 }
1934
1935 if ucChannel != ucUser {
1936 return ircError{&irc.Message{
1937 Command: irc.ERR_USERNOTINCHANNEL,
[400]1938 Params: []string{dc.nick, user, channel, "They are on another network"},
[159]1939 }}
1940 }
1941 uc := ucChannel
1942
1943 params := []string{upstreamChannel, upstreamUser}
1944 if reason != "" {
1945 params = append(params, reason)
1946 }
[757]1947 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[159]1948 Command: "KICK",
1949 Params: params,
1950 })
1951 }
[69]1952 case "MODE":
[46]1953 var name string
1954 if err := parseMessageParams(msg, &name); err != nil {
1955 return err
1956 }
1957
1958 var modeStr string
1959 if len(msg.Params) > 1 {
1960 modeStr = msg.Params[1]
1961 }
1962
[478]1963 if casemapASCII(name) == dc.nickCM {
[46]1964 if modeStr != "" {
[554]1965 if uc := dc.upstream(); uc != nil {
[757]1966 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[69]1967 Command: "MODE",
1968 Params: []string{uc.nick, modeStr},
1969 })
[554]1970 } else {
1971 dc.SendMessage(&irc.Message{
1972 Prefix: dc.srv.prefix(),
1973 Command: irc.ERR_UMODEUNKNOWNFLAG,
1974 Params: []string{dc.nick, "Cannot change user mode in multi-upstream mode"},
1975 })
1976 }
[46]1977 } else {
[553]1978 var userMode string
1979 if uc := dc.upstream(); uc != nil {
1980 userMode = string(uc.modes)
1981 }
1982
[55]1983 dc.SendMessage(&irc.Message{
1984 Prefix: dc.srv.prefix(),
[46]1985 Command: irc.RPL_UMODEIS,
[672]1986 Params: []string{dc.nick, "+" + userMode},
[54]1987 })
[46]1988 }
[139]1989 return nil
[46]1990 }
[139]1991
1992 uc, upstreamName, err := dc.unmarshalEntity(name)
1993 if err != nil {
1994 return err
1995 }
1996
1997 if !uc.isChannel(upstreamName) {
1998 return ircError{&irc.Message{
1999 Command: irc.ERR_USERSDONTMATCH,
2000 Params: []string{dc.nick, "Cannot change mode for other users"},
2001 }}
2002 }
2003
2004 if modeStr != "" {
2005 params := []string{upstreamName, modeStr}
2006 params = append(params, msg.Params[2:]...)
[757]2007 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[139]2008 Command: "MODE",
2009 Params: params,
2010 })
2011 } else {
[478]2012 ch := uc.channels.Value(upstreamName)
2013 if ch == nil {
[139]2014 return ircError{&irc.Message{
2015 Command: irc.ERR_NOSUCHCHANNEL,
2016 Params: []string{dc.nick, name, "No such channel"},
2017 }}
2018 }
2019
2020 if ch.modes == nil {
2021 // we haven't received the initial RPL_CHANNELMODEIS yet
2022 // ignore the request, we will broadcast the modes later when we receive RPL_CHANNELMODEIS
2023 return nil
2024 }
2025
2026 modeStr, modeParams := ch.modes.Format()
2027 params := []string{dc.nick, name, modeStr}
2028 params = append(params, modeParams...)
2029
2030 dc.SendMessage(&irc.Message{
2031 Prefix: dc.srv.prefix(),
2032 Command: irc.RPL_CHANNELMODEIS,
2033 Params: params,
2034 })
[162]2035 if ch.creationTime != "" {
2036 dc.SendMessage(&irc.Message{
2037 Prefix: dc.srv.prefix(),
2038 Command: rpl_creationtime,
2039 Params: []string{dc.nick, name, ch.creationTime},
2040 })
2041 }
[139]2042 }
[160]2043 case "TOPIC":
2044 var channel string
2045 if err := parseMessageParams(msg, &channel); err != nil {
2046 return err
2047 }
2048
[478]2049 uc, upstreamName, err := dc.unmarshalEntity(channel)
[160]2050 if err != nil {
2051 return err
2052 }
2053
2054 if len(msg.Params) > 1 { // setting topic
2055 topic := msg.Params[1]
[757]2056 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[160]2057 Command: "TOPIC",
[478]2058 Params: []string{upstreamName, topic},
[160]2059 })
2060 } else { // getting topic
[478]2061 ch := uc.channels.Value(upstreamName)
2062 if ch == nil {
[160]2063 return ircError{&irc.Message{
2064 Command: irc.ERR_NOSUCHCHANNEL,
[478]2065 Params: []string{dc.nick, upstreamName, "No such channel"},
[160]2066 }}
2067 }
2068 sendTopic(dc, ch)
2069 }
[177]2070 case "LIST":
[681]2071 network := dc.network
2072 if network == nil && len(msg.Params) > 0 {
2073 var err error
2074 network, msg.Params[0], err = dc.unmarshalEntityNetwork(msg.Params[0])
2075 if err != nil {
2076 return err
[177]2077 }
2078 }
[681]2079 if network == nil {
2080 dc.SendMessage(&irc.Message{
2081 Prefix: dc.srv.prefix(),
2082 Command: irc.RPL_LISTEND,
2083 Params: []string{dc.nick, "LIST without a network suffix is not supported in multi-upstream mode"},
2084 })
2085 return nil
2086 }
[177]2087
[681]2088 uc := network.conn
2089 if uc == nil {
2090 dc.SendMessage(&irc.Message{
2091 Prefix: dc.srv.prefix(),
2092 Command: irc.RPL_LISTEND,
2093 Params: []string{dc.nick, "Disconnected from upstream server"},
2094 })
2095 return nil
2096 }
2097
[682]2098 uc.enqueueCommand(dc, msg)
[140]2099 case "NAMES":
2100 if len(msg.Params) == 0 {
2101 dc.SendMessage(&irc.Message{
2102 Prefix: dc.srv.prefix(),
2103 Command: irc.RPL_ENDOFNAMES,
2104 Params: []string{dc.nick, "*", "End of /NAMES list"},
2105 })
2106 return nil
2107 }
2108
2109 channels := strings.Split(msg.Params[0], ",")
2110 for _, channel := range channels {
[478]2111 uc, upstreamName, err := dc.unmarshalEntity(channel)
[140]2112 if err != nil {
2113 return err
2114 }
2115
[478]2116 ch := uc.channels.Value(upstreamName)
2117 if ch != nil {
[140]2118 sendNames(dc, ch)
2119 } else {
2120 // NAMES on a channel we have not joined, ask upstream
[757]2121 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[140]2122 Command: "NAMES",
[478]2123 Params: []string{upstreamName},
[140]2124 })
2125 }
2126 }
[660]2127 // For WHOX docs, see:
2128 // - http://faerion.sourceforge.net/doc/irc/whox.var
2129 // - https://github.com/quakenet/snircd/blob/master/doc/readme.who
2130 // Note, many features aren't widely implemented, such as flags and mask2
[127]2131 case "WHO":
2132 if len(msg.Params) == 0 {
2133 // TODO: support WHO without parameters
2134 dc.SendMessage(&irc.Message{
2135 Prefix: dc.srv.prefix(),
2136 Command: irc.RPL_ENDOFWHO,
[140]2137 Params: []string{dc.nick, "*", "End of /WHO list"},
[127]2138 })
2139 return nil
2140 }
2141
[660]2142 // Clients will use the first mask to match RPL_ENDOFWHO
2143 endOfWhoToken := msg.Params[0]
[127]2144
[660]2145 // TODO: add support for WHOX mask2
2146 mask := msg.Params[0]
2147 var options string
2148 if len(msg.Params) > 1 {
2149 options = msg.Params[1]
2150 }
2151
2152 optionsParts := strings.SplitN(options, "%", 2)
2153 // TODO: add support for WHOX flags in optionsParts[0]
2154 var fields, whoxToken string
2155 if len(optionsParts) == 2 {
2156 optionsParts := strings.SplitN(optionsParts[1], ",", 2)
2157 fields = strings.ToLower(optionsParts[0])
2158 if len(optionsParts) == 2 && strings.Contains(fields, "t") {
2159 whoxToken = optionsParts[1]
2160 }
2161 }
2162
2163 // TODO: support mixed bouncer/upstream WHO queries
2164 maskCM := casemapASCII(mask)
2165 if dc.network == nil && maskCM == dc.nickCM {
[142]2166 // TODO: support AWAY (H/G) in self WHO reply
[658]2167 flags := "H"
2168 if dc.user.Admin {
[659]2169 flags += "*"
[658]2170 }
[660]2171 info := whoxInfo{
2172 Token: whoxToken,
2173 Username: dc.user.Username,
2174 Hostname: dc.hostname,
[691]2175 Server: dc.srv.Config().Hostname,
[660]2176 Nickname: dc.nick,
2177 Flags: flags,
[661]2178 Account: dc.user.Username,
[660]2179 Realname: dc.realname,
2180 }
2181 dc.SendMessage(generateWHOXReply(dc.srv.prefix(), dc.nick, fields, &info))
[142]2182 dc.SendMessage(&irc.Message{
2183 Prefix: dc.srv.prefix(),
2184 Command: irc.RPL_ENDOFWHO,
[660]2185 Params: []string{dc.nick, endOfWhoToken, "End of /WHO list"},
[142]2186 })
2187 return nil
2188 }
[660]2189 if maskCM == serviceNickCM {
2190 info := whoxInfo{
2191 Token: whoxToken,
2192 Username: servicePrefix.User,
2193 Hostname: servicePrefix.Host,
[691]2194 Server: dc.srv.Config().Hostname,
[660]2195 Nickname: serviceNick,
2196 Flags: "H*",
[661]2197 Account: serviceNick,
[660]2198 Realname: serviceRealname,
2199 }
2200 dc.SendMessage(generateWHOXReply(dc.srv.prefix(), dc.nick, fields, &info))
[343]2201 dc.SendMessage(&irc.Message{
2202 Prefix: dc.srv.prefix(),
2203 Command: irc.RPL_ENDOFWHO,
[660]2204 Params: []string{dc.nick, endOfWhoToken, "End of /WHO list"},
[343]2205 })
2206 return nil
2207 }
[142]2208
[660]2209 // TODO: properly support WHO masks
2210 uc, upstreamMask, err := dc.unmarshalEntity(mask)
[127]2211 if err != nil {
2212 return err
2213 }
2214
[660]2215 params := []string{upstreamMask}
2216 if options != "" {
2217 params = append(params, options)
[127]2218 }
2219
[682]2220 uc.enqueueCommand(dc, &irc.Message{
[127]2221 Command: "WHO",
2222 Params: params,
2223 })
[128]2224 case "WHOIS":
2225 if len(msg.Params) == 0 {
2226 return ircError{&irc.Message{
2227 Command: irc.ERR_NONICKNAMEGIVEN,
2228 Params: []string{dc.nick, "No nickname given"},
2229 }}
2230 }
2231
2232 var target, mask string
2233 if len(msg.Params) == 1 {
2234 target = ""
2235 mask = msg.Params[0]
2236 } else {
2237 target = msg.Params[0]
2238 mask = msg.Params[1]
2239 }
2240 // TODO: support multiple WHOIS users
2241 if i := strings.IndexByte(mask, ','); i >= 0 {
2242 mask = mask[:i]
2243 }
2244
[520]2245 if dc.network == nil && casemapASCII(mask) == dc.nickCM {
[142]2246 dc.SendMessage(&irc.Message{
2247 Prefix: dc.srv.prefix(),
2248 Command: irc.RPL_WHOISUSER,
[184]2249 Params: []string{dc.nick, dc.nick, dc.user.Username, dc.hostname, "*", dc.realname},
[142]2250 })
2251 dc.SendMessage(&irc.Message{
2252 Prefix: dc.srv.prefix(),
2253 Command: irc.RPL_WHOISSERVER,
[691]2254 Params: []string{dc.nick, dc.nick, dc.srv.Config().Hostname, "soju"},
[142]2255 })
[658]2256 if dc.user.Admin {
2257 dc.SendMessage(&irc.Message{
2258 Prefix: dc.srv.prefix(),
2259 Command: irc.RPL_WHOISOPERATOR,
2260 Params: []string{dc.nick, dc.nick, "is a bouncer administrator"},
2261 })
2262 }
[142]2263 dc.SendMessage(&irc.Message{
2264 Prefix: dc.srv.prefix(),
[661]2265 Command: rpl_whoisaccount,
2266 Params: []string{dc.nick, dc.nick, dc.user.Username, "is logged in as"},
2267 })
2268 dc.SendMessage(&irc.Message{
2269 Prefix: dc.srv.prefix(),
[142]2270 Command: irc.RPL_ENDOFWHOIS,
2271 Params: []string{dc.nick, dc.nick, "End of /WHOIS list"},
2272 })
2273 return nil
2274 }
[609]2275 if casemapASCII(mask) == serviceNickCM {
2276 dc.SendMessage(&irc.Message{
2277 Prefix: dc.srv.prefix(),
2278 Command: irc.RPL_WHOISUSER,
2279 Params: []string{dc.nick, serviceNick, servicePrefix.User, servicePrefix.Host, "*", serviceRealname},
2280 })
2281 dc.SendMessage(&irc.Message{
2282 Prefix: dc.srv.prefix(),
2283 Command: irc.RPL_WHOISSERVER,
[691]2284 Params: []string{dc.nick, serviceNick, dc.srv.Config().Hostname, "soju"},
[609]2285 })
2286 dc.SendMessage(&irc.Message{
2287 Prefix: dc.srv.prefix(),
[657]2288 Command: irc.RPL_WHOISOPERATOR,
2289 Params: []string{dc.nick, serviceNick, "is the bouncer service"},
2290 })
2291 dc.SendMessage(&irc.Message{
2292 Prefix: dc.srv.prefix(),
[661]2293 Command: rpl_whoisaccount,
2294 Params: []string{dc.nick, serviceNick, serviceNick, "is logged in as"},
2295 })
2296 dc.SendMessage(&irc.Message{
2297 Prefix: dc.srv.prefix(),
[609]2298 Command: irc.RPL_ENDOFWHOIS,
2299 Params: []string{dc.nick, serviceNick, "End of /WHOIS list"},
2300 })
2301 return nil
2302 }
[142]2303
[128]2304 // TODO: support WHOIS masks
2305 uc, upstreamNick, err := dc.unmarshalEntity(mask)
2306 if err != nil {
2307 return err
2308 }
2309
2310 var params []string
2311 if target != "" {
[299]2312 if target == mask { // WHOIS nick nick
2313 params = []string{upstreamNick, upstreamNick}
2314 } else {
2315 params = []string{target, upstreamNick}
2316 }
[128]2317 } else {
2318 params = []string{upstreamNick}
2319 }
2320
[757]2321 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[128]2322 Command: "WHOIS",
2323 Params: params,
2324 })
[562]2325 case "PRIVMSG", "NOTICE":
[58]2326 var targetsStr, text string
2327 if err := parseMessageParams(msg, &targetsStr, &text); err != nil {
2328 return err
2329 }
[303]2330 tags := copyClientTags(msg.Tags)
[58]2331
2332 for _, name := range strings.Split(targetsStr, ",") {
[691]2333 if name == "$"+dc.srv.Config().Hostname || (name == "$*" && dc.network == nil) {
[563]2334 // "$" means a server mask follows. If it's the bouncer's
2335 // hostname, broadcast the message to all bouncer users.
2336 if !dc.user.Admin {
2337 return ircError{&irc.Message{
2338 Prefix: dc.srv.prefix(),
2339 Command: irc.ERR_BADMASK,
2340 Params: []string{dc.nick, name, "Permission denied to broadcast message to all bouncer users"},
2341 }}
2342 }
2343
2344 dc.logger.Printf("broadcasting bouncer-wide %v: %v", msg.Command, text)
2345
2346 broadcastTags := tags.Copy()
[784]2347 broadcastTags["time"] = irc.TagValue(formatServerTime(time.Now()))
[563]2348 broadcastMsg := &irc.Message{
2349 Tags: broadcastTags,
2350 Prefix: servicePrefix,
2351 Command: msg.Command,
2352 Params: []string{name, text},
2353 }
2354 dc.srv.forEachUser(func(u *user) {
2355 u.events <- eventBroadcast{broadcastMsg}
2356 })
2357 continue
2358 }
2359
[529]2360 if dc.network == nil && casemapASCII(name) == dc.nickCM {
[618]2361 dc.SendMessage(&irc.Message{
2362 Tags: msg.Tags.Copy(),
2363 Prefix: dc.prefix(),
2364 Command: msg.Command,
2365 Params: []string{name, text},
2366 })
[529]2367 continue
2368 }
2369
[562]2370 if msg.Command == "PRIVMSG" && casemapASCII(name) == serviceNickCM {
[431]2371 if dc.caps["echo-message"] {
2372 echoTags := tags.Copy()
[784]2373 echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
[431]2374 dc.SendMessage(&irc.Message{
2375 Tags: echoTags,
2376 Prefix: dc.prefix(),
[562]2377 Command: msg.Command,
[431]2378 Params: []string{name, text},
2379 })
2380 }
[677]2381 handleServicePRIVMSG(ctx, dc, text)
[117]2382 continue
2383 }
2384
[127]2385 uc, upstreamName, err := dc.unmarshalEntity(name)
[58]2386 if err != nil {
2387 return err
2388 }
2389
[562]2390 if msg.Command == "PRIVMSG" && uc.network.casemap(upstreamName) == "nickserv" {
[675]2391 dc.handleNickServPRIVMSG(ctx, uc, text)
[95]2392 }
2393
[268]2394 unmarshaledText := text
2395 if uc.isChannel(upstreamName) {
2396 unmarshaledText = dc.unmarshalText(uc, text)
2397 }
[757]2398 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[303]2399 Tags: tags,
[562]2400 Command: msg.Command,
[268]2401 Params: []string{upstreamName, unmarshaledText},
[60]2402 })
[105]2403
[303]2404 echoTags := tags.Copy()
[784]2405 echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
[559]2406 if uc.account != "" {
2407 echoTags["account"] = irc.TagValue(uc.account)
2408 }
[113]2409 echoMsg := &irc.Message{
[690]2410 Tags: echoTags,
2411 Prefix: &irc.Prefix{Name: uc.nick},
[562]2412 Command: msg.Command,
[113]2413 Params: []string{upstreamName, text},
2414 }
[239]2415 uc.produce(upstreamName, echoMsg, dc)
[435]2416
2417 uc.updateChannelAutoDetach(upstreamName)
[58]2418 }
[303]2419 case "TAGMSG":
2420 var targetsStr string
2421 if err := parseMessageParams(msg, &targetsStr); err != nil {
2422 return err
2423 }
2424 tags := copyClientTags(msg.Tags)
2425
2426 for _, name := range strings.Split(targetsStr, ",") {
[617]2427 if dc.network == nil && casemapASCII(name) == dc.nickCM {
2428 dc.SendMessage(&irc.Message{
2429 Tags: msg.Tags.Copy(),
2430 Prefix: dc.prefix(),
2431 Command: "TAGMSG",
2432 Params: []string{name},
2433 })
2434 continue
2435 }
2436
[616]2437 if casemapASCII(name) == serviceNickCM {
2438 continue
2439 }
2440
[303]2441 uc, upstreamName, err := dc.unmarshalEntity(name)
2442 if err != nil {
2443 return err
2444 }
[427]2445 if _, ok := uc.caps["message-tags"]; !ok {
2446 continue
2447 }
[303]2448
[757]2449 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[303]2450 Tags: tags,
2451 Command: "TAGMSG",
2452 Params: []string{upstreamName},
2453 })
[435]2454
[780]2455 echoTags := tags.Copy()
[784]2456 echoTags["time"] = irc.TagValue(formatServerTime(time.Now()))
[780]2457 if uc.account != "" {
2458 echoTags["account"] = irc.TagValue(uc.account)
2459 }
2460 echoMsg := &irc.Message{
2461 Tags: echoTags,
2462 Prefix: &irc.Prefix{Name: uc.nick},
2463 Command: "TAGMSG",
2464 Params: []string{upstreamName},
2465 }
2466 uc.produce(upstreamName, echoMsg, dc)
2467
[435]2468 uc.updateChannelAutoDetach(upstreamName)
[303]2469 }
[163]2470 case "INVITE":
2471 var user, channel string
2472 if err := parseMessageParams(msg, &user, &channel); err != nil {
2473 return err
2474 }
2475
2476 ucChannel, upstreamChannel, err := dc.unmarshalEntity(channel)
2477 if err != nil {
2478 return err
2479 }
2480
2481 ucUser, upstreamUser, err := dc.unmarshalEntity(user)
2482 if err != nil {
2483 return err
2484 }
2485
2486 if ucChannel != ucUser {
2487 return ircError{&irc.Message{
2488 Command: irc.ERR_USERNOTINCHANNEL,
[401]2489 Params: []string{dc.nick, user, channel, "They are on another network"},
[163]2490 }}
2491 }
2492 uc := ucChannel
2493
[757]2494 uc.SendMessageLabeled(ctx, dc.id, &irc.Message{
[163]2495 Command: "INVITE",
2496 Params: []string{upstreamUser, upstreamChannel},
2497 })
[724]2498 case "AUTHENTICATE":
2499 // Post-connection-registration AUTHENTICATE is unsupported in
2500 // multi-upstream mode, or if the upstream doesn't support SASL
2501 uc := dc.upstream()
2502 if uc == nil || !uc.caps["sasl"] {
2503 return ircError{&irc.Message{
2504 Command: irc.ERR_SASLFAIL,
2505 Params: []string{dc.nick, "Upstream network authentication not supported"},
2506 }}
2507 }
2508
2509 credentials, err := dc.handleAuthenticateCommand(msg)
2510 if err != nil {
2511 return err
2512 }
2513
2514 if credentials != nil {
2515 if uc.saslClient != nil {
2516 dc.endSASL(&irc.Message{
2517 Prefix: dc.srv.prefix(),
2518 Command: irc.ERR_SASLFAIL,
2519 Params: []string{dc.nick, "Another authentication attempt is already in progress"},
2520 })
2521 return nil
2522 }
2523
2524 uc.logger.Printf("starting post-registration SASL PLAIN authentication with username %q", credentials.plainUsername)
2525 uc.saslClient = sasl.NewPlainClient("", credentials.plainUsername, credentials.plainPassword)
2526 uc.enqueueCommand(dc, &irc.Message{
2527 Command: "AUTHENTICATE",
2528 Params: []string{"PLAIN"},
2529 })
2530 }
[729]2531 case "REGISTER", "VERIFY":
2532 // Check number of params here, since we'll use that to save the
2533 // credentials on command success
2534 if (msg.Command == "REGISTER" && len(msg.Params) < 3) || (msg.Command == "VERIFY" && len(msg.Params) < 2) {
2535 return newNeedMoreParamsError(msg.Command)
2536 }
2537
2538 uc := dc.upstream()
2539 if uc == nil || !uc.caps["draft/account-registration"] {
2540 return ircError{&irc.Message{
2541 Command: "FAIL",
2542 Params: []string{msg.Command, "TEMPORARILY_UNAVAILABLE", "*", "Upstream network account registration not supported"},
2543 }}
2544 }
2545
2546 uc.logger.Printf("starting %v with account name %v", msg.Command, msg.Params[0])
2547 uc.enqueueCommand(dc, msg)
[684]2548 case "MONITOR":
2549 // MONITOR is unsupported in multi-upstream mode
2550 uc := dc.upstream()
2551 if uc == nil {
2552 return newUnknownCommandError(msg.Command)
2553 }
[742]2554 if _, ok := uc.isupport["MONITOR"]; !ok {
2555 return newUnknownCommandError(msg.Command)
2556 }
[684]2557
2558 var subcommand string
2559 if err := parseMessageParams(msg, &subcommand); err != nil {
2560 return err
2561 }
2562
2563 switch strings.ToUpper(subcommand) {
2564 case "+", "-":
2565 var targets string
2566 if err := parseMessageParams(msg, nil, &targets); err != nil {
2567 return err
2568 }
2569 for _, target := range strings.Split(targets, ",") {
2570 if subcommand == "+" {
2571 // Hard limit, just to avoid having downstreams fill our map
2572 if len(dc.monitored.innerMap) >= 1000 {
2573 dc.SendMessage(&irc.Message{
2574 Prefix: dc.srv.prefix(),
2575 Command: irc.ERR_MONLISTFULL,
2576 Params: []string{dc.nick, "1000", target, "Bouncer monitor list is full"},
2577 })
2578 continue
2579 }
2580
2581 dc.monitored.SetValue(target, nil)
2582
2583 if uc.monitored.Has(target) {
2584 cmd := irc.RPL_MONOFFLINE
2585 if online := uc.monitored.Value(target); online {
2586 cmd = irc.RPL_MONONLINE
2587 }
2588
2589 dc.SendMessage(&irc.Message{
2590 Prefix: dc.srv.prefix(),
2591 Command: cmd,
2592 Params: []string{dc.nick, target},
2593 })
2594 }
2595 } else {
2596 dc.monitored.Delete(target)
2597 }
2598 }
2599 uc.updateMonitor()
2600 case "C": // clear
2601 dc.monitored = newCasemapMap(0)
2602 uc.updateMonitor()
2603 case "L": // list
2604 // TODO: be less lazy and pack the list
2605 for _, entry := range dc.monitored.innerMap {
2606 dc.SendMessage(&irc.Message{
2607 Prefix: dc.srv.prefix(),
2608 Command: irc.RPL_MONLIST,
2609 Params: []string{dc.nick, entry.originalKey},
2610 })
2611 }
2612 dc.SendMessage(&irc.Message{
2613 Prefix: dc.srv.prefix(),
2614 Command: irc.RPL_ENDOFMONLIST,
2615 Params: []string{dc.nick, "End of MONITOR list"},
2616 })
2617 case "S": // status
2618 // TODO: be less lazy and pack the lists
2619 for _, entry := range dc.monitored.innerMap {
2620 target := entry.originalKey
2621
2622 cmd := irc.RPL_MONOFFLINE
2623 if online := uc.monitored.Value(target); online {
2624 cmd = irc.RPL_MONONLINE
2625 }
2626
2627 dc.SendMessage(&irc.Message{
2628 Prefix: dc.srv.prefix(),
2629 Command: cmd,
2630 Params: []string{dc.nick, target},
2631 })
2632 }
2633 }
[319]2634 case "CHATHISTORY":
2635 var subcommand string
2636 if err := parseMessageParams(msg, &subcommand); err != nil {
2637 return err
2638 }
[516]2639 var target, limitStr string
2640 var boundsStr [2]string
2641 switch subcommand {
[719]2642 case "AFTER", "BEFORE", "LATEST":
[516]2643 if err := parseMessageParams(msg, nil, &target, &boundsStr[0], &limitStr); err != nil {
2644 return err
2645 }
2646 case "BETWEEN":
2647 if err := parseMessageParams(msg, nil, &target, &boundsStr[0], &boundsStr[1], &limitStr); err != nil {
2648 return err
2649 }
[549]2650 case "TARGETS":
[688]2651 if dc.network == nil {
2652 // Either an unbound bouncer network, in which case we should return no targets,
2653 // or a multi-upstream downstream, but we don't support CHATHISTORY TARGETS for those yet.
2654 dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef irc.TagValue) {})
2655 return nil
2656 }
[549]2657 if err := parseMessageParams(msg, nil, &boundsStr[0], &boundsStr[1], &limitStr); err != nil {
2658 return err
2659 }
[516]2660 default:
[719]2661 // TODO: support AROUND
[319]2662 return ircError{&irc.Message{
2663 Command: "FAIL",
[516]2664 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, "Unknown command"},
[319]2665 }}
2666 }
2667
[586]2668 // We don't save history for our service
2669 if casemapASCII(target) == serviceNickCM {
2670 dc.SendBatch("chathistory", []string{target}, nil, func(batchRef irc.TagValue) {})
2671 return nil
2672 }
2673
[441]2674 store, ok := dc.user.msgStore.(chatHistoryMessageStore)
2675 if !ok {
[319]2676 return ircError{&irc.Message{
2677 Command: irc.ERR_UNKNOWNCOMMAND,
[456]2678 Params: []string{dc.nick, "CHATHISTORY", "Unknown command"},
[319]2679 }}
2680 }
2681
[585]2682 network, entity, err := dc.unmarshalEntityNetwork(target)
[319]2683 if err != nil {
2684 return err
2685 }
[585]2686 entity = network.casemap(entity)
[319]2687
2688 // TODO: support msgid criteria
[516]2689 var bounds [2]time.Time
2690 bounds[0] = parseChatHistoryBound(boundsStr[0])
[719]2691 if subcommand == "LATEST" && boundsStr[0] == "*" {
[720]2692 bounds[0] = time.Now()
[719]2693 } else if bounds[0].IsZero() {
[319]2694 return ircError{&irc.Message{
2695 Command: "FAIL",
[516]2696 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[0], "Invalid first bound"},
[319]2697 }}
2698 }
2699
[516]2700 if boundsStr[1] != "" {
2701 bounds[1] = parseChatHistoryBound(boundsStr[1])
2702 if bounds[1].IsZero() {
2703 return ircError{&irc.Message{
2704 Command: "FAIL",
2705 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, boundsStr[1], "Invalid second bound"},
2706 }}
2707 }
[319]2708 }
2709
2710 limit, err := strconv.Atoi(limitStr)
[670]2711 if err != nil || limit < 0 || limit > chatHistoryLimit {
[319]2712 return ircError{&irc.Message{
2713 Command: "FAIL",
[456]2714 Params: []string{"CHATHISTORY", "INVALID_PARAMS", subcommand, limitStr, "Invalid limit"},
[319]2715 }}
2716 }
2717
[665]2718 eventPlayback := dc.caps["draft/event-playback"]
2719
[387]2720 var history []*irc.Message
[319]2721 switch subcommand {
[719]2722 case "BEFORE", "LATEST":
[667]2723 history, err = store.LoadBeforeTime(ctx, &network.Network, entity, bounds[0], time.Time{}, limit, eventPlayback)
[360]2724 case "AFTER":
[667]2725 history, err = store.LoadAfterTime(ctx, &network.Network, entity, bounds[0], time.Now(), limit, eventPlayback)
[516]2726 case "BETWEEN":
2727 if bounds[0].Before(bounds[1]) {
[667]2728 history, err = store.LoadAfterTime(ctx, &network.Network, entity, bounds[0], bounds[1], limit, eventPlayback)
[516]2729 } else {
[667]2730 history, err = store.LoadBeforeTime(ctx, &network.Network, entity, bounds[0], bounds[1], limit, eventPlayback)
[516]2731 }
[549]2732 case "TARGETS":
2733 // TODO: support TARGETS in multi-upstream mode
[667]2734 targets, err := store.ListTargets(ctx, &network.Network, bounds[0], bounds[1], limit, eventPlayback)
[549]2735 if err != nil {
[627]2736 dc.logger.Printf("failed fetching targets for chathistory: %v", err)
[549]2737 return ircError{&irc.Message{
2738 Command: "FAIL",
2739 Params: []string{"CHATHISTORY", "MESSAGE_ERROR", subcommand, "Failed to retrieve targets"},
2740 }}
2741 }
2742
[551]2743 dc.SendBatch("draft/chathistory-targets", nil, nil, func(batchRef irc.TagValue) {
2744 for _, target := range targets {
[585]2745 if ch := network.channels.Value(target.Name); ch != nil && ch.Detached {
[551]2746 continue
2747 }
[549]2748
[551]2749 dc.SendMessage(&irc.Message{
2750 Tags: irc.Tags{"batch": batchRef},
2751 Prefix: dc.srv.prefix(),
2752 Command: "CHATHISTORY",
[784]2753 Params: []string{"TARGETS", target.Name, formatServerTime(target.LatestMessage)},
[551]2754 })
[550]2755 }
[549]2756 })
2757
2758 return nil
[319]2759 }
[387]2760 if err != nil {
[515]2761 dc.logger.Printf("failed fetching %q messages for chathistory: %v", target, err)
[387]2762 return newChatHistoryError(subcommand, target)
2763 }
2764
[551]2765 dc.SendBatch("chathistory", []string{target}, nil, func(batchRef irc.TagValue) {
2766 for _, msg := range history {
2767 msg.Tags["batch"] = batchRef
[585]2768 dc.SendMessage(dc.marshalMessage(msg, network))
[551]2769 }
[387]2770 })
[781]2771 case "READ":
2772 var target, criteria string
2773 if err := parseMessageParams(msg, &target); err != nil {
2774 return ircError{&irc.Message{
2775 Command: "FAIL",
2776 Params: []string{"READ", "NEED_MORE_PARAMS", "Missing parameters"},
2777 }}
2778 }
2779 if len(msg.Params) > 1 {
2780 criteria = msg.Params[1]
2781 }
2782
[783]2783 // We don't save read receipts for our service
2784 if casemapASCII(target) == serviceNickCM {
2785 dc.SendMessage(&irc.Message{
2786 Prefix: dc.prefix(),
2787 Command: "READ",
2788 Params: []string{target, "*"},
2789 })
2790 return nil
2791 }
2792
[781]2793 uc, entity, err := dc.unmarshalEntity(target)
2794 if err != nil {
2795 return err
2796 }
2797 entityCM := uc.network.casemap(entity)
2798
2799 r, err := dc.srv.db.GetReadReceipt(ctx, uc.network.ID, entityCM)
2800 if err != nil {
2801 dc.logger.Printf("failed to get the read receipt for %q: %v", entity, err)
2802 return ircError{&irc.Message{
2803 Command: "FAIL",
2804 Params: []string{"READ", "INTERNAL_ERROR", target, "Internal error"},
2805 }}
2806 } else if r == nil {
2807 r = &ReadReceipt{
2808 Target: entityCM,
2809 }
2810 }
2811
2812 broadcast := false
2813 if len(criteria) > 0 {
2814 // TODO: support msgid criteria
2815 criteriaParts := strings.SplitN(criteria, "=", 2)
2816 if len(criteriaParts) != 2 || criteriaParts[0] != "timestamp" {
2817 return ircError{&irc.Message{
2818 Command: "FAIL",
2819 Params: []string{"READ", "INVALID_PARAMS", criteria, "Unknown criteria"},
2820 }}
2821 }
2822
2823 timestamp, err := time.Parse(serverTimeLayout, criteriaParts[1])
2824 if err != nil {
2825 return ircError{&irc.Message{
2826 Command: "FAIL",
2827 Params: []string{"READ", "INVALID_PARAMS", criteria, "Invalid criteria"},
2828 }}
2829 }
2830 now := time.Now()
2831 if timestamp.After(now) {
2832 timestamp = now
2833 }
2834 if r.Timestamp.Before(timestamp) {
2835 r.Timestamp = timestamp
2836 if err := dc.srv.db.StoreReadReceipt(ctx, uc.network.ID, r); err != nil {
2837 dc.logger.Printf("failed to store receipt for %q: %v", entity, err)
2838 return ircError{&irc.Message{
2839 Command: "FAIL",
2840 Params: []string{"READ", "INTERNAL_ERROR", target, "Internal error"},
2841 }}
2842 }
2843 broadcast = true
2844 }
2845 }
2846
2847 timestampStr := "*"
2848 if !r.Timestamp.IsZero() {
[784]2849 timestampStr = fmt.Sprintf("timestamp=%s", formatServerTime(r.Timestamp))
[781]2850 }
2851 uc.forEachDownstream(func(d *downstreamConn) {
2852 if broadcast || dc.id == d.id {
2853 d.SendMessage(&irc.Message{
2854 Prefix: d.prefix(),
2855 Command: "READ",
2856 Params: []string{d.marshalEntity(uc.network, entity), timestampStr},
2857 })
2858 }
2859 })
[532]2860 case "BOUNCER":
2861 var subcommand string
2862 if err := parseMessageParams(msg, &subcommand); err != nil {
2863 return err
2864 }
2865
2866 switch strings.ToUpper(subcommand) {
[646]2867 case "BIND":
2868 return ircError{&irc.Message{
2869 Command: "FAIL",
2870 Params: []string{"BOUNCER", "REGISTRATION_IS_COMPLETED", "BIND", "Cannot bind to a network after registration"},
2871 }}
[532]2872 case "LISTNETWORKS":
[551]2873 dc.SendBatch("soju.im/bouncer-networks", nil, nil, func(batchRef irc.TagValue) {
[768]2874 for _, network := range dc.user.networks {
[551]2875 idStr := fmt.Sprintf("%v", network.ID)
2876 attrs := getNetworkAttrs(network)
2877 dc.SendMessage(&irc.Message{
2878 Tags: irc.Tags{"batch": batchRef},
2879 Prefix: dc.srv.prefix(),
2880 Command: "BOUNCER",
2881 Params: []string{"NETWORK", idStr, attrs.String()},
2882 })
[768]2883 }
[532]2884 })
2885 case "ADDNETWORK":
2886 var attrsStr string
2887 if err := parseMessageParams(msg, nil, &attrsStr); err != nil {
2888 return err
2889 }
2890 attrs := irc.ParseTags(attrsStr)
2891
[654]2892 record := &Network{Nick: dc.nick, Enabled: true}
2893 if err := updateNetworkAttrs(record, attrs, subcommand); err != nil {
2894 return err
[532]2895 }
2896
[664]2897 if record.Nick == dc.user.Username {
2898 record.Nick = ""
2899 }
[654]2900 if record.Realname == dc.user.Realname {
2901 record.Realname = ""
[532]2902 }
2903
[676]2904 network, err := dc.user.createNetwork(ctx, record)
[532]2905 if err != nil {
2906 return ircError{&irc.Message{
2907 Command: "FAIL",
2908 Params: []string{"BOUNCER", "UNKNOWN_ERROR", subcommand, fmt.Sprintf("Failed to create network: %v", err)},
2909 }}
2910 }
2911
2912 dc.SendMessage(&irc.Message{
2913 Prefix: dc.srv.prefix(),
2914 Command: "BOUNCER",
2915 Params: []string{"ADDNETWORK", fmt.Sprintf("%v", network.ID)},
2916 })
2917 case "CHANGENETWORK":
2918 var idStr, attrsStr string
2919 if err := parseMessageParams(msg, nil, &idStr, &attrsStr); err != nil {
2920 return err
2921 }
[535]2922 id, err := parseBouncerNetID(subcommand, idStr)
[532]2923 if err != nil {
2924 return err
2925 }
2926 attrs := irc.ParseTags(attrsStr)
2927
2928 net := dc.user.getNetworkByID(id)
2929 if net == nil {
2930 return ircError{&irc.Message{
2931 Command: "FAIL",
[535]2932 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, idStr, "Invalid network ID"},
[532]2933 }}
2934 }
2935
2936 record := net.Network // copy network record because we'll mutate it
[654]2937 if err := updateNetworkAttrs(&record, attrs, subcommand); err != nil {
2938 return err
[532]2939 }
2940
[664]2941 if record.Nick == dc.user.Username {
2942 record.Nick = ""
2943 }
[654]2944 if record.Realname == dc.user.Realname {
2945 record.Realname = ""
2946 }
2947
[676]2948 _, err = dc.user.updateNetwork(ctx, &record)
[532]2949 if err != nil {
2950 return ircError{&irc.Message{
2951 Command: "FAIL",
2952 Params: []string{"BOUNCER", "UNKNOWN_ERROR", subcommand, fmt.Sprintf("Failed to update network: %v", err)},
2953 }}
2954 }
2955
2956 dc.SendMessage(&irc.Message{
2957 Prefix: dc.srv.prefix(),
2958 Command: "BOUNCER",
2959 Params: []string{"CHANGENETWORK", idStr},
2960 })
2961 case "DELNETWORK":
2962 var idStr string
2963 if err := parseMessageParams(msg, nil, &idStr); err != nil {
2964 return err
2965 }
[535]2966 id, err := parseBouncerNetID(subcommand, idStr)
[532]2967 if err != nil {
2968 return err
2969 }
2970
2971 net := dc.user.getNetworkByID(id)
2972 if net == nil {
2973 return ircError{&irc.Message{
2974 Command: "FAIL",
[535]2975 Params: []string{"BOUNCER", "INVALID_NETID", subcommand, idStr, "Invalid network ID"},
[532]2976 }}
2977 }
2978
[676]2979 if err := dc.user.deleteNetwork(ctx, net.ID); err != nil {
[532]2980 return err
2981 }
2982
2983 dc.SendMessage(&irc.Message{
2984 Prefix: dc.srv.prefix(),
2985 Command: "BOUNCER",
2986 Params: []string{"DELNETWORK", idStr},
2987 })
2988 default:
2989 return ircError{&irc.Message{
2990 Command: "FAIL",
2991 Params: []string{"BOUNCER", "UNKNOWN_COMMAND", subcommand, "Unknown subcommand"},
2992 }}
2993 }
[13]2994 default:
[55]2995 dc.logger.Printf("unhandled message: %v", msg)
[547]2996
2997 // Only forward unknown commands in single-upstream mode
2998 uc := dc.upstream()
2999 if uc == nil {
3000 return newUnknownCommandError(msg.Command)
3001 }
3002
[757]3003 uc.SendMessageLabeled(ctx, dc.id, msg)
[13]3004 }
[42]3005 return nil
[13]3006}
[95]3007
[675]3008func (dc *downstreamConn) handleNickServPRIVMSG(ctx context.Context, uc *upstreamConn, text string) {
[95]3009 username, password, ok := parseNickServCredentials(text, uc.nick)
[724]3010 if ok {
3011 uc.network.autoSaveSASLPlain(ctx, username, password)
[95]3012 }
3013}
3014
3015func parseNickServCredentials(text, nick string) (username, password string, ok bool) {
3016 fields := strings.Fields(text)
3017 if len(fields) < 2 {
3018 return "", "", false
3019 }
3020 cmd := strings.ToUpper(fields[0])
3021 params := fields[1:]
3022 switch cmd {
3023 case "REGISTER":
3024 username = nick
3025 password = params[0]
3026 case "IDENTIFY":
3027 if len(params) == 1 {
3028 username = nick
[182]3029 password = params[0]
[95]3030 } else {
3031 username = params[0]
[182]3032 password = params[1]
[95]3033 }
[182]3034 case "SET":
3035 if len(params) == 2 && strings.EqualFold(params[0], "PASSWORD") {
3036 username = nick
3037 password = params[1]
3038 }
[340]3039 default:
3040 return "", "", false
[95]3041 }
3042 return username, password, true
3043}
Note: See TracBrowser for help on using the repository browser.